Bug 695170 - Implement visited link styles [r=blassey]

Provide an IHistory interface implementation from the android widgets
rather than using the one in Places. This allows the Link class to get
history information from the android code. The IHistory implementation
talks to the Android browser history database via JNI.
The Java-side implementation attempts to batch-process the requests coming
in, and keeps an in-memory cache of the visited links to avoid multiple
hits to the database. The in-memory cache is guaranteed to be cleared
by the VM before an OOMs get thrown.
The current implementation does not consider external events that might
update the history database, and so the visited link information
provided to Gecko might not reflect the latest information in the
Android browser history database.
This commit is contained in:
Kartikaya Gupta 2011-10-25 11:39:32 -04:00
parent 3b171f21a2
commit 32f767413d
13 changed files with 452 additions and 2 deletions

View File

@ -125,6 +125,7 @@ public class GeckoAppShell
public static native void loadLibs(String apkName, boolean shouldExtract);
public static native void onChangeNetworkLinkStatus(String status);
public static native void reportJavaCrash(String stack);
public static native void notifyUriVisited(String uri);
public static native void processNextNativeEvent();
@ -1582,4 +1583,16 @@ public class GeckoAppShell
}
return gPromptService;
}
static void checkUriVisited(String uri) { // invoked from native JNI code
GlobalHistory.getInstance().checkUriVisited(uri);
}
static void markUriVisited(final String uri) { // invoked from native JNI code
getHandler().post(new Runnable() {
public void run() {
GlobalHistory.getInstance().add(uri);
}
});
}
}

View File

@ -0,0 +1,136 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Kartikaya Gupta <kgupta@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.lang.ref.SoftReference;
import android.database.Cursor;
import android.os.Handler;
import android.provider.Browser;
import android.util.Log;
class GlobalHistory {
private static GlobalHistory sInstance = new GlobalHistory();
static GlobalHistory getInstance() {
return sInstance;
}
// this is the delay between receiving a URI check request and processing it.
// this allows batching together multiple requests and processing them together,
// which is more efficient.
private static final long BATCHING_DELAY_MS = 100;
private static final String LOG_NAME = "GlobalHistory";
private final Handler mHandler; // a background thread on which we can process requests
private final Queue<String> mPendingUris; // URIs that need to be checked
private SoftReference<Set<String>> mVisitedCache; // cache of the visited URI list
private final Runnable mNotifierRunnable; // off-thread runnable used to check URIs
private boolean mProcessing; // = false // whether or not the runnable is queued/working
private GlobalHistory() {
mHandler = GeckoAppShell.getHandler();
mPendingUris = new LinkedList<String>();
mVisitedCache = new SoftReference<Set<String>>(null);
mNotifierRunnable = new Runnable() {
public void run() {
Set<String> visitedSet = mVisitedCache.get();
if (visitedSet == null) {
// the cache was wiped away, repopulate it
Log.w(LOG_NAME, "Rebuilding visited link set...");
visitedSet = new HashSet<String>();
Cursor c = GeckoApp.mAppContext.getContentResolver().query(Browser.BOOKMARKS_URI,
new String[] { Browser.BookmarkColumns.URL },
Browser.BookmarkColumns.BOOKMARK + "=0 AND " +
Browser.BookmarkColumns.VISITS + ">0",
null,
null);
if (c.moveToFirst()) {
do {
visitedSet.add(c.getString(0));
} while (c.moveToNext());
}
mVisitedCache = new SoftReference<Set<String>>(visitedSet);
}
// this runs on the same handler thread as the checkUriVisited code,
// so no synchronization needed
while (true) {
String uri = mPendingUris.poll();
if (uri == null) {
break;
}
if (visitedSet.contains(uri)) {
GeckoAppShell.notifyUriVisited(uri);
}
}
mProcessing = false;
}
};
}
public void add(String uri) {
Browser.updateVisitedHistory(GeckoApp.mAppContext.getContentResolver(), uri, true);
Set<String> visitedSet = mVisitedCache.get();
if (visitedSet != null) {
visitedSet.add(uri);
}
GeckoAppShell.notifyUriVisited(uri);
}
public void checkUriVisited(final String uri) {
mHandler.post(new Runnable() {
public void run() {
// this runs on the same handler thread as the processing loop,
// so synchronization needed
mPendingUris.add(uri);
if (mProcessing) {
// there's already a runnable queued up or working away, so
// no need to post another
return;
}
mProcessing = true;
mHandler.postDelayed(mNotifierRunnable, BATCHING_DELAY_MS);
}
});
}
}

View File

@ -64,6 +64,7 @@ JAVAFILES = \
SurfaceInfo.java \
DoorHanger.java \
DoorHangerPopup.java \
GlobalHistory.java \
$(NULL)
PROCESSEDJAVAFILES = \

View File

@ -44,7 +44,6 @@ import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.graphics.drawable.*;
import android.util.Log;
import android.provider.Browser;
public class Tab {
@ -167,7 +166,7 @@ public class Tab {
private class HistoryEntryTask extends AsyncTask<HistoryEntry, Void, Void> {
protected Void doInBackground(HistoryEntry... entries) {
HistoryEntry entry = entries[0];
Browser.updateVisitedHistory(GeckoApp.mAppContext.getContentResolver(), entry.mUri, true);
GlobalHistory.getInstance().add(entry.mUri);
return null;
}
}

View File

@ -241,6 +241,7 @@ SHELL_WRAPPER1(onChangeNetworkLinkStatus, jstring)
SHELL_WRAPPER1(reportJavaCrash, jstring)
SHELL_WRAPPER0(executeNextRunnable)
SHELL_WRAPPER1(cameraCallbackBridge, jbyteArray)
SHELL_WRAPPER1(notifyUriVisited, jstring)
static void * xul_handle = NULL;
static time_t apk_mtime = 0;
@ -646,6 +647,7 @@ loadLibs(const char *apkName)
GETFUNC(reportJavaCrash);
GETFUNC(executeNextRunnable);
GETFUNC(cameraCallbackBridge);
GETFUNC(notifyUriVisited);
#undef GETFUNC
gettimeofday(&t1, 0);
struct rusage usage2;

View File

@ -99,6 +99,15 @@ CPPSRCS = \
LOCAL_INCLUDES += -I$(srcdir)/../build
ifeq ($(OS_TARGET),Android)
CPPSRCS += nsAndroidHistory.cpp
LOCAL_INCLUDES += \
-I$(topsrcdir)/docshell/base \
-I$(topsrcdir)/content/base/src \
$(NULL)
endif
# This is the default value. Must be in sync with the one defined in SQLite.
DEFINES += -DSQLITE_DEFAULT_PAGE_SIZE=32768

View File

@ -0,0 +1,165 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Kartikaya Gupta <kgupta@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsAndroidHistory.h"
#include "AndroidBridge.h"
#include "Link.h"
using namespace mozilla;
using mozilla::dom::Link;
NS_IMPL_ISUPPORTS1(nsAndroidHistory, IHistory)
nsAndroidHistory* nsAndroidHistory::sHistory = NULL;
/*static*/
nsAndroidHistory*
nsAndroidHistory::GetSingleton()
{
if (!sHistory) {
sHistory = new nsAndroidHistory();
NS_ENSURE_TRUE(sHistory, nsnull);
}
NS_ADDREF(sHistory);
return sHistory;
}
nsAndroidHistory::nsAndroidHistory()
{
mListeners.Init();
}
NS_IMETHODIMP
nsAndroidHistory::RegisterVisitedCallback(nsIURI *aURI, Link *aContent)
{
if (!aContent || !aURI)
return NS_OK;
nsCAutoString uri;
nsresult rv = aURI->GetSpec(uri);
if (NS_FAILED(rv)) return rv;
nsString uriString = NS_ConvertUTF8toUTF16(uri);
nsTArray<Link*>* list = mListeners.Get(uriString);
if (! list) {
list = new nsTArray<Link*>();
mListeners.Put(uriString, list);
}
list->AppendElement(aContent);
AndroidBridge *bridge = AndroidBridge::Bridge();
if (bridge) {
bridge->CheckURIVisited(uriString);
}
return NS_OK;
}
NS_IMETHODIMP
nsAndroidHistory::UnregisterVisitedCallback(nsIURI *aURI, Link *aContent)
{
if (!aContent || !aURI)
return NS_OK;
nsCAutoString uri;
nsresult rv = aURI->GetSpec(uri);
if (NS_FAILED(rv)) return rv;
nsString uriString = NS_ConvertUTF8toUTF16(uri);
nsTArray<Link*>* list = mListeners.Get(uriString);
if (! list)
return NS_OK;
list->RemoveElement(aContent);
if (list->IsEmpty()) {
mListeners.Remove(uriString);
delete list;
}
return NS_OK;
}
NS_IMETHODIMP
nsAndroidHistory::VisitURI(nsIURI *aURI, nsIURI *aLastVisitedURI, PRUint32 aFlags)
{
if (!aURI)
return NS_OK;
AndroidBridge *bridge = AndroidBridge::Bridge();
if (bridge) {
nsCAutoString uri;
nsresult rv = aURI->GetSpec(uri);
if (NS_FAILED(rv)) return rv;
nsString uriString = NS_ConvertUTF8toUTF16(uri);
bridge->MarkURIVisited(uriString);
}
return NS_OK;
}
NS_IMETHODIMP
nsAndroidHistory::SetURITitle(nsIURI *aURI, const nsAString& aTitle)
{
// we don't do anything with this right now
return NS_OK;
}
void /*static*/
nsAndroidHistory::NotifyURIVisited(const nsString& aUriString)
{
if (! sHistory)
return;
sHistory->mPendingURIs.Push(aUriString);
NS_DispatchToMainThread(sHistory);
}
NS_IMETHODIMP
nsAndroidHistory::Run()
{
while (! mPendingURIs.IsEmpty()) {
nsString uriString = mPendingURIs.Pop();
nsTArray<Link*>* list = sHistory->mListeners.Get(uriString);
if (list) {
for (unsigned int i = 0; i < list->Length(); i++) {
list->ElementAt(i)->SetLinkState(eLinkState_Visited);
}
// as per the IHistory interface contract, remove the
// Link pointers once they have been notified
mListeners.Remove(uriString);
delete list;
}
}
return NS_OK;
}

View File

@ -0,0 +1,72 @@
/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Kartikaya Gupta <kgupta@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef NS_ANDROIDHISTORY_H
#define NS_ANDROIDHISTORY_H
#include "IHistory.h"
#include "nsDataHashtable.h"
#include "nsTPriorityQueue.h"
#include "nsThreadUtils.h"
#define NS_ANDROIDHISTORY_CID \
{0xCCAA4880, 0x44DD, 0x40A7, {0xA1, 0x3F, 0x61, 0x56, 0xFC, 0x88, 0x2C, 0x0B}}
class nsAndroidHistory : public mozilla::IHistory, public nsIRunnable
{
public:
NS_DECL_ISUPPORTS
NS_DECL_IHISTORY
NS_DECL_NSIRUNNABLE
/**
* Obtains a pointer that has had AddRef called on it. Used by the service
* manager only.
*/
static nsAndroidHistory* GetSingleton();
nsAndroidHistory();
static void NotifyURIVisited(const nsString& str);
private:
static nsAndroidHistory* sHistory;
nsDataHashtable<nsStringHashKey, nsTArray<mozilla::dom::Link *> *> mListeners;
nsTPriorityQueue<nsString> mPendingURIs;
};
#endif

View File

@ -10,6 +10,10 @@
#include "History.h"
#include "nsDocShellCID.h"
#ifdef ANDROID
#include "nsAndroidHistory.h"
#endif
using namespace mozilla::places;
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsNavHistory,
@ -20,7 +24,11 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsNavBookmarks,
nsNavBookmarks::GetSingleton)
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsFaviconService,
nsFaviconService::GetSingleton)
#ifdef ANDROID
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsAndroidHistory, nsAndroidHistory::GetSingleton)
#else
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(History, History::GetSingleton)
#endif
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsPlacesImportExportService,
nsPlacesImportExportService::GetSingleton)
@ -30,7 +38,11 @@ NS_DEFINE_NAMED_CID(NS_ANNOTATIONSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_ANNOPROTOCOLHANDLER_CID);
NS_DEFINE_NAMED_CID(NS_NAVBOOKMARKSSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_FAVICONSERVICE_CID);
#ifdef ANDROID
NS_DEFINE_NAMED_CID(NS_ANDROIDHISTORY_CID);
#else
NS_DEFINE_NAMED_CID(NS_HISTORYSERVICE_CID);
#endif
NS_DEFINE_NAMED_CID(NS_PLACESIMPORTEXPORTSERVICE_CID);
const mozilla::Module::CIDEntry kPlacesCIDs[] = {
@ -39,7 +51,11 @@ const mozilla::Module::CIDEntry kPlacesCIDs[] = {
{ &kNS_ANNOPROTOCOLHANDLER_CID, false, NULL, nsAnnoProtocolHandlerConstructor },
{ &kNS_NAVBOOKMARKSSERVICE_CID, false, NULL, nsNavBookmarksConstructor },
{ &kNS_FAVICONSERVICE_CID, false, NULL, nsFaviconServiceConstructor },
#ifdef ANDROID
{ &kNS_ANDROIDHISTORY_CID, false, NULL, nsAndroidHistoryConstructor },
#else
{ &kNS_HISTORYSERVICE_CID, false, NULL, HistoryConstructor },
#endif
{ &kNS_PLACESIMPORTEXPORTSERVICE_CID, false, NULL, nsPlacesImportExportServiceConstructor },
{ NULL }
};
@ -53,7 +69,11 @@ const mozilla::Module::ContractIDEntry kPlacesContracts[] = {
{ NS_NAVBOOKMARKSSERVICE_CONTRACTID, &kNS_NAVBOOKMARKSSERVICE_CID },
{ NS_FAVICONSERVICE_CONTRACTID, &kNS_FAVICONSERVICE_CID },
{ "@mozilla.org/embeddor.implemented/bookmark-charset-resolver;1", &kNS_NAVHISTORYSERVICE_CID },
#ifdef ANDROID
{ NS_IHISTORY_CONTRACTID, &kNS_ANDROIDHISTORY_CID },
#else
{ NS_IHISTORY_CONTRACTID, &kNS_HISTORYSERVICE_CID },
#endif
{ NS_PLACESIMPORTEXPORTSERVICE_CONTRACTID, &kNS_PLACESIMPORTEXPORTSERVICE_CID },
{ NULL }
};

View File

@ -151,6 +151,8 @@ AndroidBridge::Init(JNIEnv *jEnv,
jInitCamera = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "initCamera", "(Ljava/lang/String;III)[I");
jCloseCamera = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "closeCamera", "()V");
jHandleGeckoMessage = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "handleGeckoMessage", "(Ljava/lang/String;)Ljava/lang/String;");
jCheckUriVisited = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "checkUriVisited", "(Ljava/lang/String;)V");
jMarkUriVisited = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "markUriVisited", "(Ljava/lang/String;)V");
jEGLContextClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("javax/microedition/khronos/egl/EGLContext"));
jEGL10Class = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("javax/microedition/khronos/egl/EGL10"));
@ -985,6 +987,22 @@ AndroidBridge::HandleGeckoMessage(const nsAString &aMessage, nsAString &aRet)
ALOG_BRIDGE("leaving %s", __PRETTY_FUNCTION__);
}
void
AndroidBridge::CheckURIVisited(const nsAString& aURI)
{
AutoLocalJNIFrame jniFrame(1);
jstring jstrURI = mJNIEnv->NewString(nsPromiseFlatString(aURI).get(), aURI.Length());
mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jCheckUriVisited, jstrURI);
}
void
AndroidBridge::MarkURIVisited(const nsAString& aURI)
{
AutoLocalJNIFrame jniFrame(1);
jstring jstrURI = mJNIEnv->NewString(nsPromiseFlatString(aURI).get(), aURI.Length());
mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jMarkUriVisited, jstrURI);
}
void
AndroidBridge::OpenGraphicsLibraries()
{

View File

@ -283,6 +283,9 @@ public:
void HandleGeckoMessage(const nsAString& message, nsAString &aRet);
void CheckURIVisited(const nsAString& uri);
void MarkURIVisited(const nsAString& uri);
bool InitCamera(const nsCString& contentType, PRUint32 camera, PRUint32 *width, PRUint32 *height, PRUint32 *fps);
void CloseCamera();
@ -357,6 +360,8 @@ protected:
jmethodID jInitCamera;
jmethodID jCloseCamera;
jmethodID jHandleGeckoMessage;
jmethodID jCheckUriVisited;
jmethodID jMarkUriVisited;
// stuff we need for CallEglCreateWindowSurface
jclass jEGLSurfaceImplClass;

View File

@ -50,6 +50,7 @@
#include "nsIObserverService.h"
#include "mozilla/Services.h"
#include "nsINetworkLinkService.h"
#include "nsAndroidHistory.h"
#ifdef MOZ_CRASHREPORTER
#include "nsICrashReporter.h"
@ -73,6 +74,7 @@ extern "C" {
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onChangeNetworkLinkStatus(JNIEnv *, jclass, jstring status);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_reportJavaCrash(JNIEnv *, jclass, jstring stack);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_executeNextRunnable(JNIEnv *, jclass);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyUriVisited(JNIEnv *, jclass, jstring uri);
}
@ -186,3 +188,9 @@ Java_org_mozilla_gecko_GeckoAppShell_executeNextRunnable(JNIEnv *, jclass)
AndroidBridge::Bridge()->ExecuteNextRunnable();
__android_log_print(ANDROID_LOG_INFO, "GeckoJNI", "leaving %s", __PRETTY_FUNCTION__);
}
NS_EXPORT void JNICALL
Java_org_mozilla_gecko_GeckoAppShell_notifyUriVisited(JNIEnv *jenv, jclass, jstring uri)
{
nsAndroidHistory::NotifyURIVisited(nsJNIString(uri, jenv));
}

View File

@ -97,6 +97,8 @@ LOCAL_INCLUDES += \
-I$(topsrcdir)/widget/src/xpwidgets \
-I$(topsrcdir)/widget/src/shared \
-I$(topsrcdir)/dom/system/android \
-I$(topsrcdir)/toolkit/components/places \
-I$(topsrcdir)/docshell/base \
-I$(srcdir) \
$(NULL)