mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 07:15:46 +00:00
Bug 1494713 - Implement a GeckoViewHistory
backend. r=jchen,snorp,smaug
Differential Revision: https://phabricator.services.mozilla.com/D11290 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
994f3a458c
commit
2e957c8eaf
@ -895,6 +895,9 @@ parent:
|
||||
async VisitURI(URIParams aURI, OptionalURIParams aLastVisitedURI,
|
||||
uint32_t aFlags);
|
||||
|
||||
/** Fetches the visited status for an array of URIs (Android-only). */
|
||||
async QueryVisitedState(URIParams[] aURIs);
|
||||
|
||||
/*
|
||||
* FIXME: write protocol!
|
||||
|
||||
|
@ -111,6 +111,10 @@
|
||||
#include "mozilla/a11y/nsWinUtils.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_ANDROID_HISTORY
|
||||
#include "GeckoViewHistory.h"
|
||||
#endif
|
||||
|
||||
using namespace mozilla::dom;
|
||||
using namespace mozilla::ipc;
|
||||
using namespace mozilla::layers;
|
||||
@ -3645,6 +3649,37 @@ TabParent::RecvVisitURI(const URIParams& aURI,
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
TabParent::RecvQueryVisitedState(InfallibleTArray<URIParams>&& aURIs)
|
||||
{
|
||||
#ifdef MOZ_ANDROID_HISTORY
|
||||
nsCOMPtr<IHistory> history = services::GetHistoryService();
|
||||
if (NS_WARN_IF(!history)) {
|
||||
return IPC_OK();
|
||||
}
|
||||
RefPtr<nsIWidget> widget = GetWidget();
|
||||
if (NS_WARN_IF(!widget)) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
nsTArray<nsCOMPtr<nsIURI>> uris(aURIs.Length());
|
||||
for (size_t i = 0; i < aURIs.Length(); ++i) {
|
||||
nsCOMPtr<nsIURI> uri = DeserializeURI(aURIs[i]);
|
||||
if (NS_WARN_IF(!uri)) {
|
||||
continue;
|
||||
}
|
||||
uris.AppendElement(uri);
|
||||
}
|
||||
|
||||
GeckoViewHistory* gvHistory = static_cast<GeckoViewHistory*>(history.get());
|
||||
gvHistory->QueryVisitedState(widget, uris);
|
||||
|
||||
return IPC_OK();
|
||||
#else
|
||||
return IPC_FAIL(this, "QueryVisitedState is Android-only");
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
TabParent::LiveResizeStarted()
|
||||
{
|
||||
|
@ -632,6 +632,9 @@ protected:
|
||||
RecvVisitURI(const URIParams& aURI, const OptionalURIParams& aLastVisitedURI,
|
||||
const uint32_t& aFlags) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult
|
||||
RecvQueryVisitedState(InfallibleTArray<URIParams>&& aURIs) override;
|
||||
|
||||
ContentCacheInParent mContentCache;
|
||||
|
||||
nsIntRect mRect;
|
||||
|
@ -8,22 +8,35 @@
|
||||
#include "nsShellService.h"
|
||||
|
||||
#ifdef MOZ_ANDROID_HISTORY
|
||||
#include "GeckoViewHistory.h"
|
||||
#include "nsDocShellCID.h"
|
||||
#include "nsAndroidHistory.h"
|
||||
#include "mozilla/jni/Utils.h"
|
||||
#endif
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsShellService)
|
||||
NS_DEFINE_NAMED_CID(nsShellService_CID);
|
||||
|
||||
#ifdef MOZ_ANDROID_HISTORY
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(GeckoViewHistory, GeckoViewHistory::GetSingleton)
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsAndroidHistory, nsAndroidHistory::GetSingleton)
|
||||
NS_DEFINE_NAMED_CID(NS_ANDROIDHISTORY_CID);
|
||||
|
||||
nsresult
|
||||
AndroidHistoryConstructor(nsISupports *aOuter, const nsIID& aIID,
|
||||
void **aResult)
|
||||
{
|
||||
if (mozilla::jni::IsFennec()) {
|
||||
return nsAndroidHistoryConstructor(aOuter, aIID, aResult);
|
||||
}
|
||||
return GeckoViewHistoryConstructor(aOuter, aIID, aResult);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const mozilla::Module::CIDEntry kBrowserCIDs[] = {
|
||||
{ &knsShellService_CID, false, nullptr, nsShellServiceConstructor },
|
||||
#ifdef MOZ_ANDROID_HISTORY
|
||||
{ &kNS_ANDROIDHISTORY_CID, false, nullptr, nsAndroidHistoryConstructor },
|
||||
{ &kNS_ANDROIDHISTORY_CID, false, nullptr, AndroidHistoryConstructor },
|
||||
#endif
|
||||
{ nullptr }
|
||||
};
|
||||
|
769
mobile/android/components/geckoview/GeckoViewHistory.cpp
Normal file
769
mobile/android/components/geckoview/GeckoViewHistory.cpp
Normal file
@ -0,0 +1,769 @@
|
||||
/* 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 "GeckoViewHistory.h"
|
||||
|
||||
#include "JavaBuiltins.h"
|
||||
#include "jsapi.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
|
||||
#include "mozilla/dom/ContentChild.h"
|
||||
#include "mozilla/dom/ContentParent.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/dom/Link.h"
|
||||
#include "mozilla/dom/TabChild.h"
|
||||
|
||||
#include "mozilla/ipc/URIUtils.h"
|
||||
|
||||
#include "mozilla/widget/EventDispatcher.h"
|
||||
#include "mozilla/widget/nsWindow.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
using namespace mozilla::ipc;
|
||||
using namespace mozilla::widget;
|
||||
|
||||
// Keep in sync with `GeckoSession.HistoryDelegate.VisitFlags`.
|
||||
enum class GeckoViewVisitFlags : int32_t
|
||||
{
|
||||
VISIT_TOP_LEVEL = 1 << 0,
|
||||
VISIT_REDIRECT_TEMPORARY = 1 << 1,
|
||||
VISIT_REDIRECT_PERMANENT = 1 << 2,
|
||||
VISIT_REDIRECT_SOURCE = 1 << 3,
|
||||
VISIT_REDIRECT_SOURCE_PERMANENT = 1 << 4,
|
||||
VISIT_UNRECOVERABLE_ERROR = 1 << 5,
|
||||
};
|
||||
|
||||
// The number of milliseconds to wait between tracking a link and dispatching a
|
||||
// `GetVisited` request for the link to Java. Used to debounce requests and
|
||||
// reduce the number of IPC and JNI calls.
|
||||
static const uint32_t GET_VISITS_WAIT_MS = 250;
|
||||
|
||||
static inline
|
||||
nsIDocument*
|
||||
OwnerDocForLink(Link* aLink)
|
||||
{
|
||||
Element* element = aLink->GetElement();
|
||||
return element ? element->OwnerDoc() : nullptr;
|
||||
}
|
||||
|
||||
GeckoViewHistory::GeckoViewHistory()
|
||||
{}
|
||||
|
||||
NS_IMPL_ISUPPORTS(GeckoViewHistory, IHistory, nsITimerCallback, nsINamed)
|
||||
|
||||
GeckoViewHistory::~GeckoViewHistory()
|
||||
{}
|
||||
|
||||
StaticRefPtr<GeckoViewHistory> GeckoViewHistory::sHistory;
|
||||
|
||||
/* static */
|
||||
already_AddRefed<GeckoViewHistory>
|
||||
GeckoViewHistory::GetSingleton()
|
||||
{
|
||||
if (!sHistory) {
|
||||
sHistory = new GeckoViewHistory();
|
||||
ClearOnShutdown(&sHistory);
|
||||
}
|
||||
RefPtr<GeckoViewHistory> history = sHistory;
|
||||
return history.forget();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
GeckoViewHistory::GetName(nsACString& aName)
|
||||
{
|
||||
aName.AssignLiteral("GeckoViewHistory");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Handles a request to fetch visited statuses for new tracked URIs in the
|
||||
// content process (e10s).
|
||||
void
|
||||
GeckoViewHistory::QueryVisitedStateInContentProcess()
|
||||
{
|
||||
// Holds an array of new tracked URIs for a tab in the content process.
|
||||
struct NewURIEntry
|
||||
{
|
||||
explicit NewURIEntry(TabChild* aTabChild, nsIURI* aURI)
|
||||
: mTabChild(aTabChild)
|
||||
{
|
||||
AddURI(aURI);
|
||||
}
|
||||
|
||||
void
|
||||
AddURI(nsIURI* aURI)
|
||||
{
|
||||
SerializeURI(aURI, *mURIs.AppendElement());
|
||||
}
|
||||
|
||||
TabChild* mTabChild;
|
||||
nsTArray<URIParams> mURIs;
|
||||
};
|
||||
|
||||
MOZ_ASSERT(XRE_IsContentProcess());
|
||||
|
||||
// First, serialize all the new URIs that we need to look up. Note that this
|
||||
// could be written as `nsDataHashtable<nsUint64HashKey, nsTArray<URIParams>`
|
||||
// instead, but, since we don't expect to have many tab children, we can avoid
|
||||
// the cost of hashing.
|
||||
AutoTArray<NewURIEntry, 8> newEntries;
|
||||
for (auto newURIsIter = mNewURIs.Iter(); !newURIsIter.Done(); newURIsIter.Next()) {
|
||||
nsIURI* uri = newURIsIter.Get()->GetKey();
|
||||
if (auto entry = mTrackedURIs.Lookup(uri)) {
|
||||
TrackedURI& trackedURI = entry.Data();
|
||||
if (!trackedURI.mLinks.IsEmpty()) {
|
||||
nsTObserverArray<Link*>::BackwardIterator linksIter(trackedURI.mLinks);
|
||||
while (linksIter.HasMore()) {
|
||||
Link* link = linksIter.GetNext();
|
||||
|
||||
TabChild* tabChild = nullptr;
|
||||
nsIWidget* widget =
|
||||
nsContentUtils::WidgetForContent(link->GetElement());
|
||||
if (widget) {
|
||||
tabChild = widget->GetOwningTabChild();
|
||||
}
|
||||
if (!tabChild) {
|
||||
// We need the link's tab child to find the matching window in the
|
||||
// parent process, so stop tracking it if it doesn't have one.
|
||||
linksIter.Remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add to the list of new URIs for this document, or make a new entry.
|
||||
bool hasEntry = false;
|
||||
for (NewURIEntry& entry : newEntries) {
|
||||
if (entry.mTabChild == tabChild) {
|
||||
entry.AddURI(uri);
|
||||
hasEntry = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasEntry) {
|
||||
newEntries.AppendElement(NewURIEntry(tabChild, uri));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (trackedURI.mLinks.IsEmpty()) {
|
||||
// If the list of tracked links is empty, remove the entry for the URI.
|
||||
// We'll need to query the history delegate again the next time we look
|
||||
// up the visited status for this URI.
|
||||
entry.Remove();
|
||||
}
|
||||
}
|
||||
newURIsIter.Remove();
|
||||
}
|
||||
|
||||
// Send the request to the parent process, one message per tab child.
|
||||
for (const NewURIEntry& entry : newEntries) {
|
||||
Unused << NS_WARN_IF(!entry.mTabChild->SendQueryVisitedState(entry.mURIs));
|
||||
}
|
||||
}
|
||||
|
||||
// Handles a request to fetch visited statuses for new tracked URIs in the
|
||||
// parent process (non-e10s).
|
||||
void
|
||||
GeckoViewHistory::QueryVisitedStateInParentProcess()
|
||||
{
|
||||
// Holds an array of new URIs for a window in the parent process. Unlike
|
||||
// the content process case, we don't need to track tab children, since we
|
||||
// have the outer window and can send the request directly to Java.
|
||||
struct NewURIEntry
|
||||
{
|
||||
explicit NewURIEntry(nsIWidget* aWidget, nsIURI* aURI)
|
||||
: mWidget(aWidget)
|
||||
{
|
||||
AddURI(aURI);
|
||||
}
|
||||
|
||||
void
|
||||
AddURI(nsIURI* aURI)
|
||||
{
|
||||
mURIs.AppendElement(aURI);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWidget> mWidget;
|
||||
nsTArray<nsCOMPtr<nsIURI>> mURIs;
|
||||
};
|
||||
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
|
||||
nsTArray<NewURIEntry> newEntries;
|
||||
for (auto newURIsIter = mNewURIs.Iter(); !newURIsIter.Done(); newURIsIter.Next()) {
|
||||
nsIURI* uri = newURIsIter.Get()->GetKey();
|
||||
if (auto entry = mTrackedURIs.Lookup(uri)) {
|
||||
TrackedURI& trackedURI = entry.Data();
|
||||
if (!trackedURI.mLinks.IsEmpty()) {
|
||||
nsTObserverArray<Link*>::BackwardIterator linksIter(trackedURI.mLinks);
|
||||
while (linksIter.HasMore()) {
|
||||
Link* link = linksIter.GetNext();
|
||||
|
||||
nsIWidget* widget =
|
||||
nsContentUtils::WidgetForContent(link->GetElement());
|
||||
if (!widget) {
|
||||
linksIter.Remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
bool hasEntry = false;
|
||||
for (NewURIEntry& entry : newEntries) {
|
||||
if (entry.mWidget == widget) {
|
||||
entry.AddURI(uri);
|
||||
hasEntry = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasEntry) {
|
||||
newEntries.AppendElement(NewURIEntry(widget, uri));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (trackedURI.mLinks.IsEmpty()) {
|
||||
entry.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
mNewURIs.Clear();
|
||||
|
||||
for (const NewURIEntry& entry : newEntries) {
|
||||
QueryVisitedState(entry.mWidget, entry.mURIs);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
GeckoViewHistory::Notify(nsITimer* aTimer)
|
||||
{
|
||||
MOZ_ASSERT(aTimer == mQueryVisitedStateTimer);
|
||||
|
||||
if (mNewURIs.Count() > 0) {
|
||||
if (XRE_IsContentProcess()) {
|
||||
QueryVisitedStateInContentProcess();
|
||||
} else {
|
||||
QueryVisitedStateInParentProcess();
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
GeckoViewHistory::RegisterVisitedCallback(nsIURI* aURI, Link* aLink)
|
||||
{
|
||||
if (!aLink || !aURI) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
auto entry = mTrackedURIs.LookupForAdd(aURI);
|
||||
if (entry) {
|
||||
// Start tracking the link for this URI.
|
||||
TrackedURI& trackedURI = entry.Data();
|
||||
trackedURI.mLinks.AppendElement(aLink);
|
||||
|
||||
if (trackedURI.mVisited) {
|
||||
// If we already know that the URI was visited, update the link state now.
|
||||
DispatchNotifyVisited(aURI, OwnerDocForLink(aLink));
|
||||
}
|
||||
} else {
|
||||
// Otherwise, track the link, and start the timer to request the visited
|
||||
// status from the history delegate for this and any other new URIs. If the
|
||||
// delegate reports that the URI is unvisited, we'll keep tracking the link,
|
||||
// and update its state from `VisitedCallback` once it's visited. If the URI
|
||||
// is already visited, `GetVisitedCallback` will update this and all other
|
||||
// visited links, and stop tracking them.
|
||||
entry.OrInsert([aLink]() {
|
||||
TrackedURI trackedURI;
|
||||
trackedURI.mLinks.AppendElement(aLink);
|
||||
return trackedURI;
|
||||
});
|
||||
mNewURIs.PutEntry(aURI);
|
||||
if (!mQueryVisitedStateTimer) {
|
||||
mQueryVisitedStateTimer = NS_NewTimer();
|
||||
}
|
||||
Unused << NS_WARN_IF(NS_FAILED(mQueryVisitedStateTimer->InitWithCallback(this,
|
||||
GET_VISITS_WAIT_MS, nsITimer::TYPE_ONE_SHOT)));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
GeckoViewHistory::UnregisterVisitedCallback(nsIURI* aURI, Link* aLink)
|
||||
{
|
||||
if (!aLink || !aURI) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (auto entry = mTrackedURIs.Lookup(aURI)) {
|
||||
TrackedURI& trackedURI = entry.Data();
|
||||
if (!trackedURI.mLinks.IsEmpty()) {
|
||||
nsTObserverArray<Link*>::BackwardIterator iter(trackedURI.mLinks);
|
||||
while (iter.HasMore()) {
|
||||
Link* link = iter.GetNext();
|
||||
if (link == aLink) {
|
||||
iter.Remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trackedURI.mLinks.IsEmpty()) {
|
||||
// If the list of tracked links is empty, remove the entry for the URI.
|
||||
// We'll need to query the history delegate again the next time we look
|
||||
// up the visited status for this URI.
|
||||
entry.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
mNewURIs.RemoveEntry(aURI);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the session handler for the history delegate, after the new
|
||||
* visit is recorded.
|
||||
*/
|
||||
class OnVisitedCallback final : public nsIAndroidEventCallback
|
||||
{
|
||||
public:
|
||||
explicit OnVisitedCallback(GeckoViewHistory* aHistory,
|
||||
nsIGlobalObject* aGlobalObject,
|
||||
nsIURI* aURI)
|
||||
: mHistory(aHistory)
|
||||
, mGlobalObject(aGlobalObject)
|
||||
, mURI(aURI)
|
||||
{}
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
NS_IMETHOD
|
||||
OnSuccess(JS::HandleValue aData) override
|
||||
{
|
||||
bool shouldNotify = false;
|
||||
{
|
||||
// Scope `jsapi`.
|
||||
dom::AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(mGlobalObject))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
shouldNotify = ShouldNotifyVisited(jsapi.cx(), aData);
|
||||
JS_ClearPendingException(jsapi.cx());
|
||||
}
|
||||
if (shouldNotify) {
|
||||
AutoTArray<VisitedURI, 1> visitedURIs;
|
||||
visitedURIs.AppendElement(VisitedURI{mURI, true});
|
||||
mHistory->HandleVisitedState(visitedURIs);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
OnError(JS::HandleValue aData) override
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual ~OnVisitedCallback()
|
||||
{}
|
||||
|
||||
bool
|
||||
ShouldNotifyVisited(JSContext* aCx, JS::HandleValue aData)
|
||||
{
|
||||
if (NS_WARN_IF(!aData.isBoolean())) {
|
||||
return false;
|
||||
}
|
||||
return aData.toBoolean();
|
||||
}
|
||||
|
||||
RefPtr<GeckoViewHistory> mHistory;
|
||||
nsCOMPtr<nsIGlobalObject> mGlobalObject;
|
||||
nsCOMPtr<nsIURI> mURI;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(OnVisitedCallback, nsIAndroidEventCallback)
|
||||
|
||||
NS_IMETHODIMP
|
||||
GeckoViewHistory::VisitURI(nsIWidget* aWidget,
|
||||
nsIURI* aURI,
|
||||
nsIURI* aLastVisitedURI,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
if (!aURI) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (XRE_IsContentProcess()) {
|
||||
URIParams uri;
|
||||
SerializeURI(aURI, uri);
|
||||
|
||||
OptionalURIParams lastVisitedURI;
|
||||
SerializeURI(aLastVisitedURI, lastVisitedURI);
|
||||
|
||||
// If we're in the content process, send the visit to the parent. The parent
|
||||
// will find the matching chrome window for the content process and tab,
|
||||
// then forward the visit to Java.
|
||||
if (NS_WARN_IF(!aWidget)) {
|
||||
return NS_OK;
|
||||
}
|
||||
TabChild* tabChild = aWidget->GetOwningTabChild();
|
||||
if (NS_WARN_IF(!tabChild)) {
|
||||
return NS_OK;
|
||||
}
|
||||
Unused << NS_WARN_IF(!tabChild->SendVisitURI(uri, lastVisitedURI, aFlags));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Otherwise, we're in the parent process. Wrap the URIs up in a bundle, and
|
||||
// send them to Java.
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
RefPtr<nsWindow> window = nsWindow::From(aWidget);
|
||||
if (NS_WARN_IF(!window)) {
|
||||
return NS_OK;
|
||||
}
|
||||
widget::EventDispatcher* dispatcher = window->GetEventDispatcher();
|
||||
if (NS_WARN_IF(!dispatcher)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
AutoTArray<jni::String::LocalRef, 3> keys;
|
||||
AutoTArray<jni::Object::LocalRef, 3> values;
|
||||
|
||||
nsAutoCString uriSpec;
|
||||
if (NS_WARN_IF(NS_FAILED(aURI->GetSpec(uriSpec)))) {
|
||||
return NS_OK;
|
||||
}
|
||||
keys.AppendElement(jni::StringParam(NS_LITERAL_STRING("url")));
|
||||
values.AppendElement(jni::StringParam(uriSpec));
|
||||
|
||||
if (aLastVisitedURI) {
|
||||
nsAutoCString lastVisitedURISpec;
|
||||
if (NS_WARN_IF(NS_FAILED(aLastVisitedURI->GetSpec(lastVisitedURISpec)))) {
|
||||
return NS_OK;
|
||||
}
|
||||
keys.AppendElement(jni::StringParam(NS_LITERAL_STRING("lastVisitedURL")));
|
||||
values.AppendElement(jni::StringParam(lastVisitedURISpec));
|
||||
}
|
||||
|
||||
int32_t flags = 0;
|
||||
if (aFlags & TOP_LEVEL) {
|
||||
flags |= static_cast<int32_t>(GeckoViewVisitFlags::VISIT_TOP_LEVEL);
|
||||
}
|
||||
if (aFlags & REDIRECT_TEMPORARY) {
|
||||
flags |= static_cast<int32_t>(
|
||||
GeckoViewVisitFlags::VISIT_REDIRECT_TEMPORARY);
|
||||
}
|
||||
if (aFlags & REDIRECT_PERMANENT) {
|
||||
flags |= static_cast<int32_t>(
|
||||
GeckoViewVisitFlags::VISIT_REDIRECT_PERMANENT);
|
||||
}
|
||||
if (aFlags & REDIRECT_SOURCE) {
|
||||
flags |= static_cast<int32_t>(GeckoViewVisitFlags::VISIT_REDIRECT_SOURCE);
|
||||
}
|
||||
if (aFlags & REDIRECT_SOURCE_PERMANENT) {
|
||||
flags |= static_cast<int32_t>(
|
||||
GeckoViewVisitFlags::VISIT_REDIRECT_SOURCE_PERMANENT);
|
||||
}
|
||||
if (aFlags & UNRECOVERABLE_ERROR) {
|
||||
flags |= static_cast<int32_t>(
|
||||
GeckoViewVisitFlags::VISIT_UNRECOVERABLE_ERROR);
|
||||
}
|
||||
keys.AppendElement(jni::StringParam(NS_LITERAL_STRING("flags")));
|
||||
values.AppendElement(java::sdk::Integer::ValueOf(flags));
|
||||
|
||||
MOZ_ASSERT(keys.Length() == values.Length());
|
||||
|
||||
auto bundleKeys = jni::ObjectArray::New<jni::String>(keys.Length());
|
||||
auto bundleValues = jni::ObjectArray::New<jni::Object>(values.Length());
|
||||
for (size_t i = 0; i < keys.Length(); ++i) {
|
||||
bundleKeys->SetElement(i, keys[i]);
|
||||
bundleValues->SetElement(i, values[i]);
|
||||
}
|
||||
auto bundle = java::GeckoBundle::New(bundleKeys, bundleValues);
|
||||
|
||||
nsCOMPtr<nsIAndroidEventCallback> callback = new OnVisitedCallback(this,
|
||||
dispatcher->GetGlobalObject(), aURI);
|
||||
|
||||
Unused << NS_WARN_IF(NS_FAILED(dispatcher->Dispatch(u"GeckoView:OnVisited",
|
||||
bundle,
|
||||
callback)));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
GeckoViewHistory::SetURITitle(nsIURI* aURI, const nsAString& aTitle)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
GeckoViewHistory::NotifyVisited(nsIURI* aURI)
|
||||
{
|
||||
if (NS_WARN_IF(!aURI)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (auto entry = mTrackedURIs.Lookup(aURI)) {
|
||||
TrackedURI& trackedURI = entry.Data();
|
||||
trackedURI.mVisited = true;
|
||||
nsTArray<nsIDocument*> seen;
|
||||
nsTObserverArray<Link*>::BackwardIterator iter(trackedURI.mLinks);
|
||||
while (iter.HasMore()) {
|
||||
Link* link = iter.GetNext();
|
||||
nsIDocument* doc = OwnerDocForLink(link);
|
||||
if (seen.Contains(doc)) {
|
||||
continue;
|
||||
}
|
||||
seen.AppendElement(doc);
|
||||
DispatchNotifyVisited(aURI, doc);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the session handler for the history delegate, with visited
|
||||
* statuses for all requested URIs.
|
||||
*/
|
||||
class GetVisitedCallback final : public nsIAndroidEventCallback
|
||||
{
|
||||
public:
|
||||
explicit GetVisitedCallback(GeckoViewHistory* aHistory,
|
||||
nsIGlobalObject* aGlobalObject,
|
||||
const nsTArray<nsCOMPtr<nsIURI>>& aURIs)
|
||||
: mHistory(aHistory)
|
||||
, mGlobalObject(aGlobalObject)
|
||||
, mURIs(aURIs)
|
||||
{}
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
NS_IMETHOD
|
||||
OnSuccess(JS::HandleValue aData) override
|
||||
{
|
||||
nsTArray<VisitedURI> visitedURIs;
|
||||
{
|
||||
// Scope `jsapi`.
|
||||
dom::AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(mGlobalObject))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
if (!ExtractVisitedURIs(jsapi.cx(), aData, visitedURIs)) {
|
||||
JS_ClearPendingException(jsapi.cx());
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
mHistory->HandleVisitedState(visitedURIs);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
OnError(JS::HandleValue aData) override
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual ~GetVisitedCallback()
|
||||
{}
|
||||
|
||||
/**
|
||||
* Unpacks an array of Boolean visited statuses from the session handler into
|
||||
* an array of `VisitedURI` structs. Each element in the array corresponds to
|
||||
* a URI in `mURIs`.
|
||||
*
|
||||
* Returns `false` on error, `true` if the array is `null` or was successfully
|
||||
* unpacked.
|
||||
*
|
||||
* TODO (bug 1503482): Remove this unboxing.
|
||||
*/
|
||||
bool
|
||||
ExtractVisitedURIs(JSContext* aCx, JS::HandleValue aData,
|
||||
nsTArray<VisitedURI>& aVisitedURIs)
|
||||
{
|
||||
if (aData.isNull()) {
|
||||
return true;
|
||||
}
|
||||
bool isArray = false;
|
||||
if (NS_WARN_IF(!JS_IsArrayObject(aCx, aData, &isArray))) {
|
||||
return false;
|
||||
}
|
||||
if (NS_WARN_IF(!isArray)) {
|
||||
return false;
|
||||
}
|
||||
JS::Rooted<JSObject*> visited(aCx, &aData.toObject());
|
||||
uint32_t length = 0;
|
||||
if (NS_WARN_IF(!JS_GetArrayLength(aCx, visited, &length))) {
|
||||
return false;
|
||||
}
|
||||
if (NS_WARN_IF(length != mURIs.Length())) {
|
||||
return false;
|
||||
}
|
||||
if (!aVisitedURIs.SetCapacity(length, mozilla::fallible)) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
JS::Rooted<JS::Value> value(aCx);
|
||||
if (NS_WARN_IF(!JS_GetElement(aCx, visited, i, &value))) {
|
||||
JS_ClearPendingException(aCx);
|
||||
aVisitedURIs.AppendElement(VisitedURI{mURIs[i], false});
|
||||
continue;
|
||||
}
|
||||
if (NS_WARN_IF(!value.isBoolean())) {
|
||||
aVisitedURIs.AppendElement(VisitedURI{mURIs[i], false});
|
||||
continue;
|
||||
}
|
||||
aVisitedURIs.AppendElement(VisitedURI{mURIs[i], value.toBoolean()});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
RefPtr<GeckoViewHistory> mHistory;
|
||||
nsCOMPtr<nsIGlobalObject> mGlobalObject;
|
||||
nsTArray<nsCOMPtr<nsIURI>> mURIs;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(GetVisitedCallback, nsIAndroidEventCallback)
|
||||
|
||||
/**
|
||||
* Queries the history delegate to find which URIs have been visited. This
|
||||
* is always called in the parent process: from `GetVisited` in non-e10s, and
|
||||
* from `ContentParent::RecvGetVisited` in e10s.
|
||||
*/
|
||||
void
|
||||
GeckoViewHistory::QueryVisitedState(nsIWidget* aWidget,
|
||||
const nsTArray<nsCOMPtr<nsIURI>>& aURIs)
|
||||
{
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
RefPtr<nsWindow> window = nsWindow::From(aWidget);
|
||||
if (NS_WARN_IF(!window)) {
|
||||
return;
|
||||
}
|
||||
widget::EventDispatcher* dispatcher = window->GetEventDispatcher();
|
||||
if (NS_WARN_IF(!dispatcher)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Assemble a bundle like `{ urls: ["http://example.com/1", ...] }`.
|
||||
auto uris = jni::ObjectArray::New<jni::String>(aURIs.Length());
|
||||
for (size_t i = 0; i < aURIs.Length(); ++i) {
|
||||
nsAutoCString uriSpec;
|
||||
if (NS_WARN_IF(NS_FAILED(aURIs[i]->GetSpec(uriSpec)))) {
|
||||
continue;
|
||||
}
|
||||
jni::String::LocalRef value{jni::StringParam(uriSpec)};
|
||||
uris->SetElement(i, value);
|
||||
}
|
||||
|
||||
auto bundleKeys = jni::ObjectArray::New<jni::String>(1);
|
||||
jni::String::LocalRef key(jni::StringParam(NS_LITERAL_STRING("urls")));
|
||||
bundleKeys->SetElement(0, key);
|
||||
|
||||
auto bundleValues = jni::ObjectArray::New<jni::Object>(1);
|
||||
jni::Object::LocalRef value(uris);
|
||||
bundleValues->SetElement(0, value);
|
||||
|
||||
auto bundle = java::GeckoBundle::New(bundleKeys, bundleValues);
|
||||
|
||||
nsCOMPtr<nsIAndroidEventCallback> callback = new GetVisitedCallback(this,
|
||||
dispatcher->GetGlobalObject(), aURIs);
|
||||
|
||||
Unused << NS_WARN_IF(NS_FAILED(dispatcher->Dispatch(u"GeckoView:GetVisited",
|
||||
bundle,
|
||||
callback)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates link states for all tracked links, forwarding the visited statuses to
|
||||
* the content process in e10s. This is always called in the parent process,
|
||||
* from `VisitedCallback::OnSuccess` and `GetVisitedCallback::OnSuccess`.
|
||||
*/
|
||||
void
|
||||
GeckoViewHistory::HandleVisitedState(const nsTArray<VisitedURI>& aVisitedURIs)
|
||||
{
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
if (aVisitedURIs.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<ContentParent*> cplist;
|
||||
ContentParent::GetAll(cplist);
|
||||
if (!cplist.IsEmpty()) {
|
||||
nsTArray<URIParams> visitedURIs(aVisitedURIs.Length());
|
||||
for (const VisitedURI& visitedURI : aVisitedURIs) {
|
||||
if (!visitedURI.mVisited) {
|
||||
continue;
|
||||
}
|
||||
URIParams uri;
|
||||
SerializeURI(visitedURI.mURI, uri);
|
||||
visitedURIs.AppendElement(uri);
|
||||
}
|
||||
if (visitedURIs.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (ContentParent* cp : cplist) {
|
||||
Unused << NS_WARN_IF(!cp->SendNotifyVisited(visitedURIs));
|
||||
}
|
||||
}
|
||||
|
||||
// We might still have child processes even if e10s is disabled, so always
|
||||
// check if we're tracking any links in the parent, and notify them if so.
|
||||
if (mTrackedURIs.Count() > 0) {
|
||||
for (const VisitedURI& visitedURI : aVisitedURIs) {
|
||||
if (visitedURI.mVisited) {
|
||||
Unused << NS_WARN_IF(NS_FAILED(NotifyVisited(visitedURI.mURI)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously updates the link state for all links associated with `aURI` in
|
||||
* `aDocument`. This is mostly copied from `History::DispatchNotifyVisited` and
|
||||
* `History::NotifyVisitedForDocument`.
|
||||
*/
|
||||
void
|
||||
GeckoViewHistory::DispatchNotifyVisited(nsIURI* aURI, nsIDocument* aDocument)
|
||||
{
|
||||
// Capture strong references to the arguments to capture in the closure.
|
||||
RefPtr<GeckoViewHistory> kungFuDeathGrip(this);
|
||||
nsCOMPtr<nsIDocument> doc(aDocument);
|
||||
nsCOMPtr<nsIURI> uri(aURI);
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
|
||||
"GeckoViewHistory::DispatchNotifyVisited",
|
||||
[this, kungFuDeathGrip, uri, doc] {
|
||||
nsAutoScriptBlocker scriptBlocker;
|
||||
auto entry = mTrackedURIs.Lookup(uri);
|
||||
if (NS_WARN_IF(!entry)) {
|
||||
return;
|
||||
}
|
||||
TrackedURI& trackedURI = entry.Data();
|
||||
if (!trackedURI.mLinks.IsEmpty()) {
|
||||
nsTObserverArray<Link*>::BackwardIterator iter(trackedURI.mLinks);
|
||||
while (iter.HasMore()) {
|
||||
Link* link = iter.GetNext();
|
||||
if (OwnerDocForLink(link) == doc) {
|
||||
link->SetLinkState(eLinkState_Visited);
|
||||
iter.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (trackedURI.mLinks.IsEmpty()) {
|
||||
entry.Remove();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (doc) {
|
||||
Unused << NS_WARN_IF(NS_FAILED(doc->Dispatch(TaskCategory::Other,
|
||||
runnable.forget())));
|
||||
} else {
|
||||
Unused << NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(runnable.forget())));
|
||||
}
|
||||
}
|
72
mobile/android/components/geckoview/GeckoViewHistory.h
Normal file
72
mobile/android/components/geckoview/GeckoViewHistory.h
Normal file
@ -0,0 +1,72 @@
|
||||
/* 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/. */
|
||||
|
||||
#ifndef GECKOVIEWHISTORY_H
|
||||
#define GECKOVIEWHISTORY_H
|
||||
|
||||
#include "IHistory.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsTObserverArray.h"
|
||||
#include "nsURIHashKey.h"
|
||||
#include "nsINamed.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsIURI.h"
|
||||
|
||||
#include "mozilla/StaticPtr.h"
|
||||
|
||||
class nsIDocument;
|
||||
class nsIWidget;
|
||||
|
||||
struct VisitedURI
|
||||
{
|
||||
nsCOMPtr<nsIURI> mURI;
|
||||
bool mVisited = false;
|
||||
};
|
||||
|
||||
struct TrackedURI
|
||||
{
|
||||
// Per `IHistory`, these are not owning references.
|
||||
nsTObserverArray<mozilla::dom::Link*> mLinks;
|
||||
bool mVisited = false;
|
||||
};
|
||||
|
||||
class GeckoViewHistory final : public mozilla::IHistory
|
||||
, public nsITimerCallback
|
||||
, public nsINamed
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_IHISTORY
|
||||
NS_DECL_NSITIMERCALLBACK
|
||||
NS_DECL_NSINAMED
|
||||
|
||||
static already_AddRefed<GeckoViewHistory> GetSingleton();
|
||||
|
||||
GeckoViewHistory();
|
||||
|
||||
void QueryVisitedState(nsIWidget* aWidget,
|
||||
const nsTArray<nsCOMPtr<nsIURI>>& aURIs);
|
||||
void HandleVisitedState(const nsTArray<VisitedURI>& aVisitedURIs);
|
||||
|
||||
private:
|
||||
virtual ~GeckoViewHistory();
|
||||
|
||||
void QueryVisitedStateInContentProcess();
|
||||
void QueryVisitedStateInParentProcess();
|
||||
void DispatchNotifyVisited(nsIURI* aURI, nsIDocument* aDocument);
|
||||
|
||||
static mozilla::StaticRefPtr<GeckoViewHistory> sHistory;
|
||||
|
||||
// A map of unvisited links for URIs. If the history delegate reports that
|
||||
// the URI is visited, we'll asynchronously notify and remove the links.
|
||||
nsDataHashtable<nsURIHashKey, TrackedURI> mTrackedURIs;
|
||||
|
||||
// A set of URIs for which we don't know the visited status, and need to
|
||||
// query the history delegate.
|
||||
nsTHashtable<nsURIHashKey> mNewURIs;
|
||||
|
||||
nsCOMPtr<nsITimer> mQueryVisitedStateTimer;
|
||||
};
|
||||
|
||||
#endif
|
@ -4,6 +4,21 @@
|
||||
# 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/.
|
||||
|
||||
if CONFIG['MOZ_ANDROID_HISTORY']:
|
||||
EXPORTS += [
|
||||
'GeckoViewHistory.h',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
'GeckoViewHistory.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/docshell/base',
|
||||
'/dom/base',
|
||||
]
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'GeckoView.manifest',
|
||||
'GeckoViewExternalAppService.js',
|
||||
@ -11,3 +26,5 @@ EXTRA_COMPONENTS += [
|
||||
'GeckoViewPrompt.js',
|
||||
'GeckoViewStartup.js',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
Loading…
Reference in New Issue
Block a user