Merge mozilla-central into mozilla-inbound

This commit is contained in:
Ehsan Akhgari 2012-06-02 14:41:07 -04:00
Родитель b05cd8343d 568a309d6d
Коммит 4fff8472d8
57 изменённых файлов: 1749 добавлений и 345 удалений

Просмотреть файл

@ -454,7 +454,7 @@ nsHTMLMediaElement::GetSrc(JSContext* aCtx, jsval *aParams)
NS_IMETHODIMP
nsHTMLMediaElement::SetSrc(JSContext* aCtx, const jsval & aParams)
{
if (aParams.isObject()) { // not JSVAL_IS_OBJECT() because a null object is still an object
if (aParams.isObject()) {
nsCOMPtr<nsIDOMMediaStream> stream;
stream = do_QueryInterface(nsContentUtils::XPConnect()->
GetNativeOfWrapper(aCtx, JSVAL_TO_OBJECT(aParams)));

Просмотреть файл

@ -5,7 +5,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BluetoothAdapter.h"
#include "BluetoothFirmware.h"
#include "BluetoothUtils.h"
#include "nsDOMClassInfo.h"
#include "nsDOMEvent.h"
@ -36,3 +36,29 @@ NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
NS_IMPL_ADDREF_INHERITED(BluetoothAdapter, nsDOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(BluetoothAdapter, nsDOMEventTargetHelper)
BluetoothAdapter::BluetoothAdapter(const nsCString& name) :
mName(name)
{
}
BluetoothAdapter::~BluetoothAdapter()
{
if (NS_FAILED(UnregisterBluetoothEventHandler(mName, this))) {
NS_WARNING("Failed to unregister object with observer!");
}
}
// static
already_AddRefed<BluetoothAdapter>
BluetoothAdapter::Create(const nsCString& name) {
nsRefPtr<BluetoothAdapter> adapter = new BluetoothAdapter(name);
if (NS_FAILED(RegisterBluetoothEventHandler(name, adapter))) {
NS_WARNING("Failed to register object with observer!");
return NULL;
}
return adapter.forget();
}
void BluetoothAdapter::Notify(const BluetoothEvent& aData) {
printf("Got an adapter message!\n");
}

Просмотреть файл

@ -11,6 +11,7 @@
#include "nsDOMEventTargetHelper.h"
#include "nsIDOMBluetoothAdapter.h"
#include "nsIDOMDOMRequest.h"
#include "mozilla/Observer.h"
class nsIEventTarget;
@ -18,6 +19,7 @@ BEGIN_BLUETOOTH_NAMESPACE
class BluetoothAdapter : public nsDOMEventTargetHelper
, public nsIDOMBluetoothAdapter
, public BluetoothEventObserver
{
public:
NS_DECL_ISUPPORTS_INHERITED
@ -28,6 +30,16 @@ public:
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothAdapter,
nsDOMEventTargetHelper)
static already_AddRefed<BluetoothAdapter>
Create(const nsCString& name);
void Notify(const BluetoothEvent& aParam);
protected:
nsCString mName;
private:
BluetoothAdapter() {}
BluetoothAdapter(const nsCString& name);
~BluetoothAdapter();
};
END_BLUETOOTH_NAMESPACE

Просмотреть файл

@ -7,6 +7,10 @@
#ifndef mozilla_dom_bluetooth_bluetoothcommon_h__
#define mozilla_dom_bluetooth_bluetoothcommon_h__
#include "nsString.h"
#include "nsTArray.h"
#include "mozilla/Observer.h"
#define BEGIN_BLUETOOTH_NAMESPACE \
namespace mozilla { namespace dom { namespace bluetooth {
#define END_BLUETOOTH_NAMESPACE \
@ -14,6 +18,48 @@
#define USING_BLUETOOTH_NAMESPACE \
using namespace mozilla::dom::bluetooth;
class nsIDOMBluetooth;
class nsCString;
BEGIN_BLUETOOTH_NAMESPACE
/**
* BluetoothEvents usually hand back one of 3 types:
*
* - 32-bit Int
* - String
* - Bool
*
* BluetoothVariant encases the types into a single structure.
*/
struct BluetoothVariant
{
uint32_t mUint32;
nsCString mString;
};
/**
* BluetoothNamedVariant is a variant with a name value, for passing around
* things like properties with variant values.
*/
struct BluetoothNamedVariant
{
nsCString mName;
BluetoothVariant mValue;
};
/**
* BluetoothEvent holds a variant value and the name of an event, such as
* PropertyChanged or DeviceFound.
*/
struct BluetoothEvent
{
nsCString mEventName;
nsTArray<BluetoothNamedVariant> mValues;
};
typedef mozilla::Observer<BluetoothEvent> BluetoothEventObserver;
typedef mozilla::ObserverList<BluetoothEvent> BluetoothEventObserverList;
END_BLUETOOTH_NAMESPACE
#endif // mozilla_dom_bluetooth_bluetoothcommon_h__

Просмотреть файл

@ -8,6 +8,7 @@
#include "BluetoothCommon.h"
#include "BluetoothFirmware.h"
#include "BluetoothAdapter.h"
#include "BluetoothUtils.h"
#include "nsIDocument.h"
#include "nsIURI.h"
@ -30,6 +31,7 @@
#define DOM_BLUETOOTH_URL_PREF "dom.mozBluetooth.whitelist"
using namespace mozilla;
using mozilla::Preferences;
USING_BLUETOOTH_NAMESPACE
@ -37,14 +39,16 @@ USING_BLUETOOTH_NAMESPACE
static void
FireEnabled(bool aResult, nsIDOMDOMRequest* aDomRequest)
{
nsCOMPtr<nsIDOMRequestService> rs = do_GetService("@mozilla.org/dom/dom-request-service;1");
nsCOMPtr<nsIDOMRequestService> rs =
do_GetService("@mozilla.org/dom/dom-request-service;1");
if (!rs) {
NS_WARNING("No DOMRequest Service!");
return;
}
mozilla::DebugOnly<nsresult> rv = aResult ?
DebugOnly<nsresult> rv =
aResult ?
rs->FireSuccess(aDomRequest, JSVAL_VOID) :
rs->FireError(aDomRequest,
NS_LITERAL_STRING("Bluetooth firmware loading failed"));
@ -120,7 +124,8 @@ class ToggleBtResultTask : public nsRunnable
class ToggleBtTask : public nsRunnable
{
public:
ToggleBtTask(bool aEnabled, nsIDOMDOMRequest* aReq, BluetoothManager* aManager)
ToggleBtTask(bool aEnabled, nsIDOMDOMRequest* aReq,
BluetoothManager* aManager)
: mEnabled(aEnabled),
mManagerPtr(aManager),
mDOMRequest(aReq)
@ -177,11 +182,19 @@ class ToggleBtTask : public nsRunnable
};
BluetoothManager::BluetoothManager(nsPIDOMWindow *aWindow) :
mEnabled(false)
mEnabled(false),
mName(nsDependentCString("/"))
{
BindToOwner(aWindow);
}
BluetoothManager::~BluetoothManager()
{
if(NS_FAILED(UnregisterBluetoothEventHandler(mName, this))) {
NS_WARNING("Failed to unregister object with observer!");
}
}
NS_IMETHODIMP
BluetoothManager::SetEnabled(bool aEnabled, nsIDOMDOMRequest** aDomRequest)
{
@ -220,12 +233,32 @@ BluetoothManager::GetEnabled(bool* aEnabled)
NS_IMETHODIMP
BluetoothManager::GetDefaultAdapter(nsIDOMBluetoothAdapter** aAdapter)
{
//TODO: Implement adapter fetching
nsCString path;
nsresult rv = GetDefaultAdapterPathInternal(path);
if(NS_FAILED(rv)) {
NS_WARNING("Cannot fetch adapter path!");
return NS_ERROR_FAILURE;
}
nsRefPtr<BluetoothAdapter> adapter = BluetoothAdapter::Create(path);
adapter.forget(aAdapter);
return NS_OK;
}
// static
already_AddRefed<BluetoothManager>
BluetoothManager::Create(nsPIDOMWindow* aWindow) {
nsRefPtr<BluetoothManager> manager = new BluetoothManager(aWindow);
nsDependentCString name("/");
if(NS_FAILED(RegisterBluetoothEventHandler(name, manager))) {
NS_WARNING("Failed to register object with observer!");
return NULL;
}
return manager.forget();
}
nsresult
NS_NewBluetoothManager(nsPIDOMWindow* aWindow, nsIDOMBluetoothManager** aBluetoothManager)
NS_NewBluetoothManager(nsPIDOMWindow* aWindow,
nsIDOMBluetoothManager** aBluetoothManager)
{
NS_ASSERTION(aWindow, "Null pointer!");
@ -238,8 +271,12 @@ NS_NewBluetoothManager(nsPIDOMWindow* aWindow, nsIDOMBluetoothManager** aBluetoo
return NS_OK;
}
nsRefPtr<BluetoothManager> bluetoothManager = new BluetoothManager(aWindow);
nsRefPtr<BluetoothManager> bluetoothManager = BluetoothManager::Create(aWindow);
bluetoothManager.forget(aBluetoothManager);
return NS_OK;
}
void BluetoothManager::Notify(const BluetoothEvent& aData) {
printf("Received an manager message!\n");
}

Просмотреть файл

@ -10,13 +10,14 @@
#include "BluetoothCommon.h"
#include "nsDOMEventTargetHelper.h"
#include "nsIDOMBluetoothManager.h"
#include "nsWeakReference.h"
#include "mozilla/Observer.h"
BEGIN_BLUETOOTH_NAMESPACE
class BluetoothAdapter;
class BluetoothManager : public nsDOMEventTargetHelper
, public nsIDOMBluetoothManager
, public BluetoothEventObserver
{
public:
NS_DECL_ISUPPORTS_INHERITED
@ -27,11 +28,18 @@ public:
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothManager,
nsDOMEventTargetHelper)
BluetoothManager(nsPIDOMWindow*);
inline void SetEnabledInternal(bool aEnabled) {mEnabled = aEnabled;}
static already_AddRefed<BluetoothManager>
Create(nsPIDOMWindow* aWindow);
void Notify(const BluetoothEvent& aData);
private:
BluetoothManager() {}
BluetoothManager(nsPIDOMWindow* aWindow);
~BluetoothManager();
bool mEnabled;
nsCString mName;
NS_DECL_EVENT_HANDLER(enabled)
@ -40,6 +48,7 @@ private:
END_BLUETOOTH_NAMESPACE
nsresult NS_NewBluetoothManager(nsPIDOMWindow* aWindow, nsIDOMBluetoothManager** aBluetoothManager);
nsresult NS_NewBluetoothManager(nsPIDOMWindow* aWindow,
nsIDOMBluetoothManager** aBluetoothManager);
#endif

Просмотреть файл

@ -0,0 +1,77 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=40: */
/* 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 mozilla_dom_bluetooth_bluetoothutils_h__
#define mozilla_dom_bluetooth_bluetoothutils_h__
#include "BluetoothCommon.h"
BEGIN_BLUETOOTH_NAMESPACE
/**
* BluetoothUtil functions are used to dispatch messages to Bluetooth DOM
* objects on the main thread, as well as provide platform indenpendent access
* to BT functionality. Tasks for polling for outside messages will usually
* happen on the IO Thread (see ipc/dbus for instance), and these messages will
* be encased in runnables that will then be distributed via observers managed
* here.
*/
/**
* Add a message handler object from message distribution observer.
* Must be called from the main thread.
*
* @param aNodeName Node name of the object
* @param aMsgHandler Weak pointer to the object
*
* @return NS_OK on successful addition to observer, NS_ERROR_FAILED
* otherwise
*/
nsresult RegisterBluetoothEventHandler(const nsCString& aNodeName,
BluetoothEventObserver *aMsgHandler);
/**
* Remove a message handler object from message distribution observer.
* Must be called from the main thread.
*
* @param aNodeName Node name of the object
* @param aMsgHandler Weak pointer to the object
*
* @return NS_OK on successful removal from observer service,
* NS_ERROR_FAILED otherwise
*/
nsresult UnregisterBluetoothEventHandler(const nsCString& aNodeName,
BluetoothEventObserver *aMsgHandler);
/**
* Returns the path of the default adapter, implemented via a platform
* specific method.
*
* @return Default adapter path/name on success, NULL otherwise
*/
nsresult GetDefaultAdapterPathInternal(nsCString& aAdapterPath);
/**
* Set up variables and start the platform specific connection. Must
* be called from main thread.
*
* @return NS_OK if connection starts successfully, NS_ERROR_FAILURE
* otherwise
*/
nsresult StartBluetoothConnection();
/**
* Stop the platform specific connection. Must be called from main
* thread.
*
* @return NS_OK if connection starts successfully, NS_ERROR_FAILURE
* otherwise
*/
nsresult StopBluetoothConnection();
END_BLUETOOTH_NAMESPACE
#endif

Просмотреть файл

@ -15,6 +15,16 @@ XPIDL_MODULE = dom_bluetooth
LIBXUL_LIBRARY = 1
FORCE_STATIC_LIB = 1
ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
VPATH += $(srcdir)/linux
LOCAL_INCLUDES += $(MOZ_DBUS_CFLAGS)
endif
ifdef MOZ_ENABLE_DBUS
VPATH += $(srcdir)/linux
LOCAL_INCLUDES += $(MOZ_DBUS_CFLAGS)
endif
include $(topsrcdir)/dom/dom-config.mk
CPPSRCS = \
@ -23,23 +33,23 @@ CPPSRCS = \
BluetoothFirmware.cpp \
$(NULL)
ifdef MOZ_ENABLE_DBUS
CPPSRCS += BluetoothDBusUtils.cpp
endif
ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
CPPSRCS += BluetoothDBusUtils.cpp
endif
XPIDLSRCS = \
nsIDOMNavigatorBluetooth.idl \
nsIDOMBluetoothManager.idl \
nsIDOMBluetoothAdapter.idl \
$(NULL)
ifneq (gonk,$(MOZ_WIDGET_TOOLKIT))
CFLAGS += $(MOZ_DBUS_CFLAGS)
CXXFLAGS += $(MOZ_DBUS_CFLAGS) -DHAVE_PTHREADS
endif
include $(topsrcdir)/config/rules.mk
ifeq (Linux,$(OS_TARGET))
ifdef MOZ_ENABLE_DBUS
CFLAGS += $(MOZ_DBUS_GLIB_CFLAGS)
CXXFLAGS += $(MOZ_DBUS_GLIB_CFLAGS) -DHAVE_PTHREADS
endif
endif

Просмотреть файл

@ -0,0 +1,270 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=40: */
/*
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#include "BluetoothUtils.h"
#include <cstdio>
#include <dbus/dbus.h>
#include "nsAutoPtr.h"
#include "nsThreadUtils.h"
#include "nsDebug.h"
#include "nsClassHashtable.h"
#include "mozilla/ipc/DBusUtils.h"
#include "mozilla/ipc/RawDBusConnection.h"
using namespace mozilla::ipc;
namespace mozilla {
namespace dom {
namespace bluetooth {
static nsAutoPtr<RawDBusConnection> sDBusConnection;
#undef LOG
#if defined(MOZ_WIDGET_GONK)
#include <android/log.h>
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GonkDBus", args);
#else
#define BTDEBUG true
#define LOG(args...) if (BTDEBUG) printf(args);
#endif
#define DBUS_ADAPTER_IFACE BLUEZ_DBUS_BASE_IFC ".Adapter"
#define DBUS_DEVICE_IFACE BLUEZ_DBUS_BASE_IFC ".Device"
#define BLUEZ_DBUS_BASE_PATH "/org/bluez"
#define BLUEZ_DBUS_BASE_IFC "org.bluez"
#define BLUEZ_ERROR_IFC "org.bluez.Error"
static const char* BLUETOOTH_DBUS_SIGNALS[] =
{
"type='signal',interface='org.freedesktop.DBus'",
"type='signal',interface='org.bluez.Adapter'",
"type='signal',interface='org.bluez.Manager'",
"type='signal',interface='org.bluez.Device'",
"type='signal',interface='org.bluez.Input'",
"type='signal',interface='org.bluez.Network'",
"type='signal',interface='org.bluez.NetworkServer'",
"type='signal',interface='org.bluez.HealthDevice'",
"type='signal',interface='org.bluez.AudioSink'"
};
typedef nsClassHashtable<nsCStringHashKey, BluetoothEventObserverList >
BluetoothEventObserverTable;
static nsAutoPtr<BluetoothEventObserverTable> sBluetoothEventObserverTable;
nsresult
RegisterBluetoothEventHandler(const nsCString& aNodeName,
BluetoothEventObserver* aHandler)
{
MOZ_ASSERT(NS_IsMainThread());
BluetoothEventObserverList *ol;
if (!sBluetoothEventObserverTable->Get(aNodeName, &ol)) {
sBluetoothEventObserverTable->Put(aNodeName,
new BluetoothEventObserverList());
}
sBluetoothEventObserverTable->Get(aNodeName, &ol);
ol->AddObserver(aHandler);
return NS_OK;
}
nsresult
UnregisterBluetoothEventHandler(const nsCString& aNodeName,
BluetoothEventObserver* aHandler)
{
MOZ_ASSERT(NS_IsMainThread());
BluetoothEventObserverList *ol;
if (!sBluetoothEventObserverTable->Get(aNodeName, &ol)) {
NS_WARNING("Node does not exist to remove BluetoothEventListener from!");
return NS_ERROR_FAILURE;
}
sBluetoothEventObserverTable->Get(aNodeName, &ol);
ol->RemoveObserver(aHandler);
if (ol->Length() == 0) {
sBluetoothEventObserverTable->Remove(aNodeName);
}
return NS_OK;
}
struct DistributeDBusMessageTask : public nsRunnable {
DistributeDBusMessageTask(DBusMessage* aMsg) : mMsg(aMsg)
{
}
NS_IMETHOD Run()
{
if (dbus_message_get_path(mMsg.get()) == NULL) {
return NS_OK;
}
MOZ_ASSERT(NS_IsMainThread());
// Notify observers that a message has been sent
nsDependentCString path(dbus_message_get_path(mMsg.get()));
nsDependentCString member(dbus_message_get_member(mMsg.get()));
BluetoothEventObserverList *ol;
if (!sBluetoothEventObserverTable->Get(path, &ol)) {
LOG("No objects registered for %s, returning\n",
dbus_message_get_path(mMsg.get()));
return NS_OK;
}
BluetoothEvent e;
e.mEventName = member;
ol->Broadcast(e);
return NS_OK;
}
DBusMessageRefPtr mMsg;
};
// Called by dbus during WaitForAndDispatchEventNative()
// This function is called on the IOThread
static DBusHandlerResult
EventFilter(DBusConnection *aConn, DBusMessage *aMsg,
void *aData)
{
DBusError err;
dbus_error_init(&err);
if (dbus_message_get_type(aMsg) != DBUS_MESSAGE_TYPE_SIGNAL) {
LOG("%s: not interested (not a signal).\n", __FUNCTION__);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
LOG("%s: Received signal %s:%s from %s\n", __FUNCTION__,
dbus_message_get_interface(aMsg), dbus_message_get_member(aMsg),
dbus_message_get_path(aMsg));
// TODO: Parse DBusMessage* on the IOThread and return as a BluetoothEvent so
// we aren't passing the pointer at all, as well as offloading parsing (not
// that it's that heavy.)
nsCOMPtr<DistributeDBusMessageTask> t(new DistributeDBusMessageTask(aMsg));
if (NS_FAILED(NS_DispatchToMainThread(t))) {
NS_WARNING("Failed to dispatch to main thread!");
}
return DBUS_HANDLER_RESULT_HANDLED;
}
nsresult
StartBluetoothConnection()
{
if(sDBusConnection) {
NS_WARNING("DBusConnection already established, skipping");
return NS_OK;
}
sBluetoothEventObserverTable = new BluetoothEventObserverTable();
sBluetoothEventObserverTable->Init(100);
sDBusConnection = new RawDBusConnection();
sDBusConnection->EstablishDBusConnection();
// Add a filter for all incoming messages_base
if (!dbus_connection_add_filter(sDBusConnection->mConnection, EventFilter,
NULL, NULL)) {
NS_WARNING("Cannot create DBus Event Filter for DBus Thread!");
return NS_ERROR_FAILURE;
}
return NS_OK;
}
nsresult
StopBluetoothConnection()
{
if(!sDBusConnection) {
NS_WARNING("DBusConnection does not exist, nothing to stop, skipping.");
return NS_OK;
}
dbus_connection_remove_filter(sDBusConnection->mConnection, EventFilter, NULL);
sDBusConnection = NULL;
sBluetoothEventObserverTable->Clear();
sBluetoothEventObserverTable = NULL;
return NS_OK;
}
nsresult
GetDefaultAdapterPathInternal(nsCString& aAdapterPath)
{
DBusMessage *msg = NULL, *reply = NULL;
DBusError err;
const char *device_path = NULL;
int attempt = 0;
for (attempt = 0; attempt < 1000 && reply == NULL; attempt ++) {
msg = dbus_message_new_method_call("org.bluez", "/",
"org.bluez.Manager", "DefaultAdapter");
if (!msg) {
LOG("%s: Can't allocate new method call for get_adapter_path!",
__FUNCTION__);
return NS_ERROR_FAILURE;
}
dbus_message_append_args(msg, DBUS_TYPE_INVALID);
dbus_error_init(&err);
reply = dbus_connection_send_with_reply_and_block(
sDBusConnection->mConnection, msg, -1, &err);
if (!reply) {
if (dbus_error_is_set(&err)) {
if (dbus_error_has_name(&err,
"org.freedesktop.DBus.Error.ServiceUnknown")) {
// bluetoothd is still down, retry
LOG("Service unknown\n");
dbus_error_free(&err);
//usleep(10000); // 10 ms
continue;
} else if (dbus_error_has_name(&err,
"org.bluez.Error.NoSuchAdapter")) {
LOG("No adapter found\n");
dbus_error_free(&err);
goto failed;
} else {
// Some other error we weren't expecting
LOG("other error\n");
dbus_error_free(&err);
}
}
}
}
if (attempt == 1000) {
LOG("timeout\n");
//printfE("Time out while trying to get Adapter path, is bluetoothd up ?");
goto failed;
}
if (!dbus_message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH,
&device_path, DBUS_TYPE_INVALID)
|| !device_path) {
if (dbus_error_is_set(&err)) {
dbus_error_free(&err);
}
goto failed;
}
dbus_message_unref(msg);
aAdapterPath = nsDependentCString(device_path);
return NS_OK;
failed:
dbus_message_unref(msg);
return NS_ERROR_FAILURE;
}
}
}
}

Просмотреть файл

@ -258,18 +258,19 @@ static dom::ConstantSpec gWinProperties[] =
JSObject *GetOrCreateObjectProperty(JSContext *cx, JSObject *aObject,
const char *aProperty)
{
jsval val;
if (JS_GetProperty(cx, aObject, aProperty, &val)
&& !JSVAL_IS_VOID(val)) {
if (JSVAL_IS_OBJECT(val)) {
return JSVAL_TO_OBJECT(val);
JS::Value val;
if (!JS_GetProperty(cx, aObject, aProperty, &val)) {
return NULL;
}
else {
if (!val.isUndefined()) {
if (val.isObject()) {
return &val.toObject();
}
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_UNEXPECTED_TYPE, aProperty, "not an object");
return NULL;
}
}
return JS_DefineObject(cx, aObject, aProperty, NULL, NULL, JSPROP_ENUMERATE);
}

Просмотреть файл

@ -22,6 +22,7 @@
#ifdef MOZ_B2G_BT
#include "mozilla/ipc/DBusThread.h"
#include "BluetoothFirmware.h"
#include "BluetoothUtils.h"
#endif
#include "nsContentUtils.h"
#include "nsServiceManagerUtils.h"
@ -234,6 +235,7 @@ SystemWorkerManager::Init()
NS_WARNING("Failed to initialize Bluetooth!");
return rv;
}
#endif
#ifdef MOZ_WIDGET_GONK
@ -395,6 +397,7 @@ SystemWorkerManager::InitBluetooth(JSContext *cx)
if(EnsureBluetoothInit()) {
#endif
StartDBus();
StartBluetoothConnection();
#ifdef MOZ_WIDGET_GONK
}
else {

Просмотреть файл

@ -45,18 +45,21 @@
#include "base/eintr_wrapper.h"
#include "base/message_loop.h"
#include "nsTArray.h"
#include "nsDataHashtable.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Monitor.h"
#include "mozilla/Util.h"
#include "mozilla/FileUtils.h"
#include "nsAutoPtr.h"
#include "nsThreadUtils.h"
#include "nsIThread.h"
#include "nsXULAppAPI.h"
#include "nsServiceManagerUtils.h"
#include "nsCOMPtr.h"
#undef LOG
#if defined(MOZ_WIDGET_GONK)
#include <android/log.h>
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GonkBluetooth", args);
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GonkDBus", args);
#else
#define BTDEBUG true
#define LOG(args...) if(BTDEBUG) printf(args);
@ -82,6 +85,7 @@ static const char* DBUS_SIGNALS[] =
{
"type='signal',interface='org.freedesktop.DBus'",
"type='signal',interface='org.bluez.Adapter'",
"type='signal',interface='org.bluez.Manager'",
"type='signal',interface='org.bluez.Device'",
"type='signal',interface='org.bluez.Input'",
"type='signal',interface='org.bluez.Network'",
@ -291,27 +295,6 @@ static void HandleWatchRemove(DBusThread* aDbt) {
aDbt->mWatchData.RemoveElementAt(index);
}
// Called by dbus during WaitForAndDispatchEventNative()
static DBusHandlerResult
EventFilter(DBusConnection *aConn, DBusMessage *aMsg,
void *aData)
{
DBusError err;
dbus_error_init(&err);
if (dbus_message_get_type(aMsg) != DBUS_MESSAGE_TYPE_SIGNAL) {
LOG("%s: not interested (not a signal).\n", __FUNCTION__);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
LOG("%s: Received signal %s:%s from %s\n", __FUNCTION__,
dbus_message_get_interface(aMsg), dbus_message_get_member(aMsg),
dbus_message_get_path(aMsg));
return DBUS_HANDLER_RESULT_HANDLED;
}
// DBus Thread Implementation
DBusThread::DBusThread() : mMutex("DBusGonk.mMutex")
@ -337,12 +320,9 @@ DBusThread::SetUpEventLoop()
dbus_error_init(&err);
// If we can't establish a connection to dbus, nothing else will work
if(!Create()) {
return false;
}
// Add a filter for all incoming messages_base
if (!dbus_connection_add_filter(mConnection, EventFilter, this, NULL)){
nsresult rv = EstablishDBusConnection();
if(NS_FAILED(rv)) {
NS_WARNING("Cannot create DBus Connection for DBus Thread!");
return false;
}
@ -365,7 +345,7 @@ DBusThread::SetUpEventLoop()
bool
DBusThread::TearDownData()
{
LOG("Removing DBus Bluetooth Sockets\n");
LOG("Removing DBus Sockets\n");
if (mControlFdW.get()) {
mControlFdW.dispose();
}
@ -397,7 +377,6 @@ DBusThread::TearDownEventLoop()
}
}
dbus_connection_remove_filter(mConnection, EventFilter, this);
return true;
}
@ -411,7 +390,7 @@ DBusThread::EventLoop(void *aPtr)
RemoveWatch, ToggleWatch, aPtr, NULL);
dbt->mIsRunning = true;
LOG("DBus Bluetooth Event Loop Starting\n");
LOG("DBus Event Loop Starting\n");
while (1) {
poll(dbt->mPollData.Elements(), dbt->mPollData.Length(), -1);
@ -427,7 +406,7 @@ DBusThread::EventLoop(void *aPtr)
switch (data) {
case DBUS_EVENT_LOOP_EXIT:
{
LOG("DBus Bluetooth Event Loop Exiting\n");
LOG("DBus Event Loop Exiting\n");
dbus_connection_set_watch_functions(dbt->mConnection,
NULL, NULL, NULL, NULL, NULL);
dbt->TearDownEventLoop();
@ -495,7 +474,7 @@ DBusThread::StartEventLoop()
TearDownData();
return false;
}
LOG("DBus Bluetooth Thread Starting\n");
LOG("DBus Thread Starting\n");
pthread_create(&(mThread), NULL, DBusThread::EventLoop, this);
return true;
}
@ -508,12 +487,12 @@ DBusThread::StopEventLoop()
char data = DBUS_EVENT_LOOP_EXIT;
ssize_t wret = write(mControlFdW.get(), &data, sizeof(char));
if(wret < 0) {
LOG("Cannot write exit bit to DBus Bluetooth Thread!\n");
LOG("Cannot write exit bit to DBus Thread!\n");
}
void *ret;
LOG("DBus Bluetooth Thread Joining\n");
LOG("DBus Thread Joining\n");
pthread_join(mThread, &ret);
LOG("DBus Bluetooth Thread Joined\n");
LOG("DBus Thread Joined\n");
TearDownData();
}
mIsRunning = false;
@ -535,6 +514,7 @@ ConnectDBus(Monitor* aMonitor, bool* aSuccess)
NS_WARNING("Trying to start DBus Thread that is already currently running, skipping.");
return;
}
sDBusThread = new DBusThread();
*aSuccess = true;
if(!sDBusThread->StartEventLoop())
@ -554,6 +534,7 @@ DisconnectDBus(Monitor* aMonitor, bool* aSuccess)
NS_WARNING("Trying to shutdown DBus Thread that is not currently running, skipping.");
return;
}
*aSuccess = true;
sDBusThread->StopEventLoop();
sDBusThread = NULL;

Просмотреть файл

@ -7,18 +7,30 @@
#ifndef mozilla_ipc_dbus_gonk_dbusthread_h__
#define mozilla_ipc_dbus_gonk_dbusthread_h__
struct DBusMessage;
namespace mozilla {
namespace ipc {
class nsCString;
// Starts the DBus thread, which handles returning signals to objects
// that call asynchronous functions. This should be called from the
// main thread at startup.
/**
* Starts the DBus thread, which handles returning signals to objects
* that call asynchronous functions. This should be called from the
* main thread at startup.
*
* @return True on thread starting correctly, false otherwise
*/
bool StartDBus();
// Stop the DBus thread, assuming it's currently running. Should be
// called from main thread.
/**
* Stop the DBus thread, assuming it's currently running. Should be
* called from main thread.
*
* @return True on thread stopping correctly, false otherwise
*/
bool StopDBus();
}
}
#endif

Просмотреть файл

@ -16,8 +16,9 @@
** limitations under the License.
*/
#include <stdio.h>
#include "dbus/dbus.h"
#include "DBusUtils.h"
#include <cstdio>
#include <cstring>
#undef LOG
#if defined(MOZ_WIDGET_GONK)
@ -27,6 +28,10 @@
#define LOG(args...) printf(args);
#endif
#define BLUEZ_DBUS_BASE_PATH "/org/bluez"
#define BLUEZ_DBUS_BASE_IFC "org.bluez"
#define BLUEZ_ERROR_IFC "org.bluez.Error"
namespace mozilla {
namespace ipc {
@ -43,5 +48,216 @@ log_and_free_dbus_error(DBusError* err, const char* function, DBusMessage* msg)
dbus_error_free((err));
}
typedef struct {
void (*user_cb)(DBusMessage *, void *, void *);
void *user;
void *nat;
} dbus_async_call_t;
void dbus_func_args_async_callback(DBusPendingCall *call, void *data) {
dbus_async_call_t *req = (dbus_async_call_t *)data;
DBusMessage *msg;
/* This is guaranteed to be non-NULL, because this function is called only
when once the remote method invokation returns. */
msg = dbus_pending_call_steal_reply(call);
if (msg) {
if (req->user_cb) {
// The user may not deref the message object.
req->user_cb(msg, req->user, req->nat);
}
dbus_message_unref(msg);
}
//dbus_message_unref(req->method);
dbus_pending_call_cancel(call);
dbus_pending_call_unref(call);
free(req);
}
static dbus_bool_t dbus_func_args_async_valist(DBusConnection *conn,
int timeout_ms,
void (*user_cb)(DBusMessage *,
void *,
void*),
void *user,
void *nat,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
va_list args) {
DBusMessage *msg = NULL;
const char *name;
dbus_async_call_t *pending;
dbus_bool_t reply = FALSE;
/* Compose the command */
msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, path, ifc, func);
if (msg == NULL) {
LOG("Could not allocate D-Bus message object!");
goto done;
}
/* append arguments */
if (!dbus_message_append_args_valist(msg, first_arg_type, args)) {
LOG("Could not append argument to method call!");
goto done;
}
/* Make the call. */
pending = (dbus_async_call_t *)malloc(sizeof(dbus_async_call_t));
if (pending) {
DBusPendingCall *call;
pending->user_cb = user_cb;
pending->user = user;
pending->nat = nat;
//pending->method = msg;
reply = dbus_connection_send_with_reply(conn, msg,
&call,
timeout_ms);
if (reply == TRUE) {
dbus_pending_call_set_notify(call,
dbus_func_args_async_callback,
pending,
NULL);
}
}
done:
if (msg) dbus_message_unref(msg);
return reply;
}
dbus_bool_t dbus_func_args_async(DBusConnection *conn,
int timeout_ms,
void (*reply)(DBusMessage *, void *, void*),
void *user,
void *nat,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
...) {
dbus_bool_t ret;
va_list lst;
va_start(lst, first_arg_type);
ret = dbus_func_args_async_valist(conn,
timeout_ms,
reply, user, nat,
path, ifc, func,
first_arg_type, lst);
va_end(lst);
return ret;
}
// If err is NULL, then any errors will be LOG'd, and free'd and the reply
// will be NULL.
// If err is not NULL, then it is assumed that dbus_error_init was already
// called, and error's will be returned to the caller without logging. The
// return value is NULL iff an error was set. The client must free the error if
// set.
DBusMessage * dbus_func_args_timeout_valist(DBusConnection *conn,
int timeout_ms,
DBusError *err,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
va_list args) {
DBusMessage *msg = NULL, *reply = NULL;
const char *name;
bool return_error = (err != NULL);
if (!return_error) {
err = (DBusError*)malloc(sizeof(DBusError));
dbus_error_init(err);
}
/* Compose the command */
msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, path, ifc, func);
if (msg == NULL) {
LOG("Could not allocate D-Bus message object!");
goto done;
}
/* append arguments */
if (!dbus_message_append_args_valist(msg, first_arg_type, args)) {
LOG("Could not append argument to method call!");
goto done;
}
/* Make the call. */
reply = dbus_connection_send_with_reply_and_block(conn, msg, timeout_ms, err);
if (!return_error && dbus_error_is_set(err)) {
//LOG_AND_FREE_DBUS_ERROR_WITH_MSG(err, msg);
}
done:
if (!return_error) {
free(err);
}
if (msg) dbus_message_unref(msg);
return reply;
}
DBusMessage * dbus_func_args_timeout(DBusConnection *conn,
int timeout_ms,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
...) {
DBusMessage *ret;
va_list lst;
va_start(lst, first_arg_type);
ret = dbus_func_args_timeout_valist(conn, timeout_ms, NULL,
path, ifc, func,
first_arg_type, lst);
va_end(lst);
return ret;
}
DBusMessage * dbus_func_args(DBusConnection *conn,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
...) {
DBusMessage *ret;
va_list lst;
va_start(lst, first_arg_type);
ret = dbus_func_args_timeout_valist(conn, -1, NULL,
path, ifc, func,
first_arg_type, lst);
va_end(lst);
return ret;
}
DBusMessage * dbus_func_args_error(DBusConnection *conn,
DBusError *err,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
...) {
DBusMessage *ret;
va_list lst;
va_start(lst, first_arg_type);
ret = dbus_func_args_timeout_valist(conn, -1, err,
path, ifc, func,
first_arg_type, lst);
va_end(lst);
return ret;
}
}
}

Просмотреть файл

@ -19,6 +19,9 @@
#ifndef mozilla_ipc_dbus_dbusutils_h__
#define mozilla_ipc_dbus_dbusutils_h__
#include <dbus/dbus.h>
#include "mozilla/Scoped.h"
// LOGE and free a D-Bus error
// Using #define so that __FUNCTION__ resolves usefully
#define LOG_AND_FREE_DBUS_ERROR_WITH_MSG(err, msg) log_and_free_dbus_error(err, __FUNCTION__, msg);
@ -29,7 +32,71 @@ struct DBusError;
namespace mozilla {
namespace ipc {
void log_and_free_dbus_error(DBusError* err, const char* function, DBusMessage* msg = NULL);
class DBusMessageRefPtr
{
public:
DBusMessageRefPtr(DBusMessage* aMsg) : mMsg(aMsg)
{
if (mMsg) dbus_message_ref(mMsg);
}
~DBusMessageRefPtr()
{
if (mMsg) dbus_message_unref(mMsg);
}
operator DBusMessage*() { return mMsg; }
DBusMessage* get() { return mMsg; }
private:
DBusMessage* mMsg;
};
void log_and_free_dbus_error(DBusError* err,
const char* function,
DBusMessage* msg = NULL);
dbus_bool_t dbus_func_args_async(DBusConnection *conn,
int timeout_ms,
void (*reply)(DBusMessage *, void *, void *),
void *user,
void *nat,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
...);
DBusMessage * dbus_func_args(DBusConnection *conn,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
...);
DBusMessage * dbus_func_args_error(DBusConnection *conn,
DBusError *err,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
...);
DBusMessage * dbus_func_args_timeout(DBusConnection *conn,
int timeout_ms,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
...);
DBusMessage * dbus_func_args_timeout_valist(DBusConnection *conn,
int timeout_ms,
DBusError *err,
const char *path,
const char *ifc,
const char *func,
int first_arg_type,
va_list args);
}
}

Просмотреть файл

@ -15,15 +15,19 @@ RawDBusConnection::RawDBusConnection() {
RawDBusConnection::~RawDBusConnection() {
}
bool RawDBusConnection::Create() {
nsresult RawDBusConnection::EstablishDBusConnection() {
DBusError err;
dbus_error_init(&err);
mConnection = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
if (dbus_error_is_set(&err)) {
dbus_error_free(&err);
return false;
return NS_ERROR_FAILURE;
}
dbus_connection_set_exit_on_disconnect(mConnection, FALSE);
return true;
return NS_OK;
}
void RawDBusConnection::ScopedDBusConnectionPtrTraits::release(DBusConnection* ptr)
{
if(ptr) dbus_connection_unref(ptr);
}

Просмотреть файл

@ -12,8 +12,8 @@
#include <stdio.h>
#include <string>
#include <stdlib.h>
#include "nscore.h"
#include "mozilla/Scoped.h"
#include "dbus/dbus.h"
struct DBusConnection;
@ -24,14 +24,13 @@ class RawDBusConnection
{
struct ScopedDBusConnectionPtrTraits : ScopedFreePtrTraits<DBusConnection>
{
static void release(DBusConnection* ptr) { if(ptr) dbus_connection_unref(ptr); }
static void release(DBusConnection* ptr);
};
public:
RawDBusConnection();
~RawDBusConnection();
bool Create();
protected:
nsresult EstablishDBusConnection();
Scoped<ScopedDBusConnectionPtrTraits> mConnection;
};

Просмотреть файл

@ -289,6 +289,13 @@ frontend::CompileFunctionBody(JSContext *cx, JSFunction *fun,
fn->pn_body = NULL;
fn->pn_cookie.makeFree();
ParseNode *argsbody = ListNode::create(PNK_ARGSBODY, &parser);
if (!argsbody)
return false;
argsbody->setOp(JSOP_NOP);
argsbody->makeEmpty();
fn->pn_body = argsbody;
unsigned nargs = fun->nargs;
if (nargs) {
/*

Просмотреть файл

@ -5103,52 +5103,8 @@ EmitStatementList(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t
ParseNode *pnchild = pn->pn_head;
// Destructuring is handled in args body for functions with default
// arguments.
if (pn->pn_xflags & PNX_DESTRUCT && bce->sc->fun()->hasDefaults())
if (pn->pn_xflags & PNX_DESTRUCT)
pnchild = pnchild->pn_next;
if (pn->pn_xflags & PNX_FUNCDEFS) {
/*
* This block contains top-level function definitions. To ensure
* that we emit the bytecode defining them before the rest of code
* in the block we use a separate pass over functions. During the
* main pass later the emitter will add JSOP_NOP with source notes
* for the function to preserve the original functions position
* when decompiling.
*
* Currently this is used only for functions, as compile-as-we go
* mode for scripts does not allow separate emitter passes.
*/
JS_ASSERT(bce->sc->inFunction);
if (pn->pn_xflags & PNX_DESTRUCT && !bce->sc->fun()->hasDefaults()) {
/*
* Assign the destructuring arguments before defining any
* functions, see bug 419662.
*/
JS_ASSERT(pnchild->isKind(PNK_SEMI));
JS_ASSERT(pnchild->pn_kid->isKind(PNK_VAR) || pnchild->pn_kid->isKind(PNK_CONST));
if (!EmitTree(cx, bce, pnchild))
return false;
pnchild = pnchild->pn_next;
}
for (ParseNode *pn2 = pnchild; pn2; pn2 = pn2->pn_next) {
if (pn2->isKind(PNK_FUNCTION)) {
if (pn2->isOp(JSOP_NOP)) {
if (!EmitTree(cx, bce, pn2))
return false;
} else {
/*
* JSOP_DEFFUN in a top-level block with function
* definitions appears, for example, when "if (true)"
* is optimized away from "if (true) function x() {}".
* See bug 428424.
*/
JS_ASSERT(pn2->isOp(JSOP_DEFFUN));
}
}
}
}
for (ParseNode *pn2 = pnchild; pn2; pn2 = pn2->pn_next) {
if (!EmitTree(cx, bce, pn2))
@ -5869,12 +5825,7 @@ static bool
EmitDefaults(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
JS_ASSERT(pn->isKind(PNK_ARGSBODY));
ParseNode *pnlast = pn->last();
unsigned ndefaults = 0;
for (ParseNode *arg = pn->pn_head; arg != pnlast; arg = arg->pn_next) {
if (arg->pn_expr)
ndefaults++;
}
uint16_t ndefaults = bce->sc->funbox->ndefaults;
JSFunction *fun = bce->sc->fun();
unsigned nformal = fun->nargs - fun->hasRest();
EMIT_UINT16_IMM_OP(JSOP_ACTUALSFILLED, nformal - ndefaults);
@ -5884,23 +5835,55 @@ EmitDefaults(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
return false;
jsbytecode *pc = bce->code(top + JUMP_OFFSET_LEN);
JS_ASSERT(nformal >= ndefaults);
SET_JUMP_OFFSET(pc, nformal - ndefaults);
uint16_t defstart = nformal - ndefaults;
SET_JUMP_OFFSET(pc, defstart);
pc += JUMP_OFFSET_LEN;
SET_JUMP_OFFSET(pc, nformal - 1);
pc += JUMP_OFFSET_LEN;
// Fill body of switch, which sets defaults where needed.
for (ParseNode *arg = pn->pn_head; arg != pnlast; arg = arg->pn_next) {
if (!arg->pn_expr)
unsigned i;
ParseNode *arg, *pnlast = pn->last();
for (arg = pn->pn_head, i = 0; arg != pnlast; arg = arg->pn_next, i++) {
if (!(arg->pn_dflags & PND_DEFAULT))
continue;
SET_JUMP_OFFSET(pc, bce->offset() - top);
pc += JUMP_OFFSET_LEN;
if (!EmitTree(cx, bce, arg->pn_expr))
ParseNode *expr;
if (arg->isKind(PNK_NAME)) {
expr = arg->expr();
} else {
// The argument name is bound to a function. We still have to
// evaluate the default in case it has side effects.
JS_ASSERT(!arg->isDefn());
JS_ASSERT(arg->isKind(PNK_ASSIGN));
expr = arg->pn_right;
}
if (!EmitTree(cx, bce, expr))
return false;
if (arg->isKind(PNK_NAME)) {
if (!BindNameToSlot(cx, bce, arg))
return false;
if (!EmitVarOp(cx, arg, JSOP_SETARG, bce))
return false;
} else {
// Create a dummy JSOP_SETLOCAL for the decompiler. Jump over it
// with a JSOP_GOTO in real code.
if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
return false;
ptrdiff_t hop = bce->offset();
if (EmitJump(cx, bce, JSOP_GOTO, 0) < 0)
return false;
unsigned slot;
bce->sc->bindings.lookup(cx, arg->pn_left->atom(), &slot);
// It doesn't matter if this is correct with respect to aliasing or
// not. Only the decompiler is going to see it.
if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, slot, bce))
return false;
SET_JUMP_OFFSET(bce->code(hop), bce->offset() - hop);
}
if (Emit1(cx, bce, JSOP_POP) < 0)
return false;
}
@ -5932,22 +5915,52 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
JSFunction *fun = bce->sc->fun();
ParseNode *pnlast = pn->last();
if (fun->hasDefaults()) {
// Carefully emit everything in the right order:
// 1. Destructuring
// 2. Functions
// 3. Defaults
ParseNode *pnchild = pnlast->pn_head;
if (pnlast->pn_xflags & PNX_DESTRUCT) {
JS_ASSERT(pnlast->pn_head->isKind(PNK_SEMI));
// Defaults must be able to access destructured arguments, so do
// that now.
if (!EmitTree(cx, bce, pnlast->pn_head))
// Assign the destructuring arguments before defining any functions,
// see bug 419662.
JS_ASSERT(pnchild->isKind(PNK_SEMI));
JS_ASSERT(pnchild->pn_kid->isKind(PNK_VAR) || pnchild->pn_kid->isKind(PNK_CONST));
if (!EmitTree(cx, bce, pnchild))
return false;
pnchild = pnchild->pn_next;
}
if (pnlast->pn_xflags & PNX_FUNCDEFS) {
// This block contains top-level function definitions. To ensure
// that we emit the bytecode defining them before the rest of code
// in the block we use a separate pass over functions. During the
// main pass later the emitter will add JSOP_NOP with source notes
// for the function to preserve the original functions position
// when decompiling.
// Currently this is used only for functions, as compile-as-we go
// mode for scripts does not allow separate emitter passes.
for (ParseNode *pn2 = pnchild; pn2; pn2 = pn2->pn_next) {
if (pn2->isKind(PNK_FUNCTION)) {
if (pn2->isOp(JSOP_NOP)) {
if (!EmitTree(cx, bce, pn2))
return false;
} else {
// JSOP_DEFFUN in a top-level block with function
// definitions appears, for example, when "if (true)"
// is optimized away from "if (true) function x() {}".
// See bug 428424.
JS_ASSERT(pn2->isOp(JSOP_DEFFUN));
}
}
}
}
if (fun->hasDefaults()) {
ParseNode *rest = NULL;
if (fun->hasRest()) {
JS_ASSERT(!bce->sc->funArgumentsHasLocalBinding());
// Defaults and rest also need special handling. The rest
// parameter needs to be undefined while defaults are being
// Defaults with a rest parameter need special handling. The
// rest parameter needs to be undefined while defaults are being
// processed. To do this, we create the rest argument and let it
// sit on the stack while processing defaults. The rest
// parameter's slot is set to undefined for the course of
@ -5984,7 +5997,6 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
return JS_FALSE;
}
if (pn2->pn_next == pnlast && fun->hasRest() && !fun->hasDefaults()) {
// Fill rest parameter. We handled the case with defaults above.
JS_ASSERT(!bce->sc->funArgumentsHasLocalBinding());
bce->switchToProlog();

Просмотреть файл

@ -738,6 +738,7 @@ struct ParseNode {
still valid, but this use no longer
optimizable via an upvar opcode */
#define PND_CLOSED 0x200 /* variable is closed over */
#define PND_DEFAULT 0x400 /* definition is an arg with a default */
/* Flags to propagate from uses to definition. */
#define PND_USE2DEF_FLAGS (PND_ASSIGNED | PND_CLOSED)
@ -1406,7 +1407,7 @@ struct Definition : public ParseNode
enum Kind { VAR, CONST, LET, FUNCTION, ARG, UNKNOWN };
bool isBindingForm() { return int(kind()) <= int(LET); }
bool canHaveInitializer() { return int(kind()) <= int(LET) || kind() == ARG; }
static const char *kindString(Kind kind);
@ -1510,6 +1511,7 @@ struct FunctionBox : public ObjectBox
FunctionBox *parent;
Bindings bindings; /* bindings for this function */
uint32_t level:JSFB_LEVEL_BITS;
uint16_t ndefaults;
bool queued:1;
bool inLoop:1; /* in a loop in parent function */
bool inWith:1; /* some enclosing scope is a with-statement

Просмотреть файл

@ -178,6 +178,7 @@ FunctionBox::FunctionBox(ObjectBox* traceListHead, JSObject *obj, ParseNode *fn,
parent(tc->sc->funbox),
bindings(tc->sc->context),
level(tc->sc->staticLevel),
ndefaults(0),
queued(false),
inLoop(false),
inWith(!!tc->innermostWith),
@ -870,7 +871,7 @@ MakeDefIntoUse(Definition *dn, ParseNode *pn, JSAtom *atom, Parser *parser)
* must rewrite it to be an assignment node, whose freshly allocated
* left-hand side becomes a use of pn.
*/
if (dn->isBindingForm()) {
if (dn->canHaveInitializer()) {
ParseNode *rhs = dn->expr();
if (rhs) {
ParseNode *lhs = MakeAssignment(dn, rhs, parser);
@ -930,14 +931,6 @@ js::DefineArg(ParseNode *pn, JSAtom *atom, unsigned i, Parser *parser)
return false;
ParseNode *argsbody = pn->pn_body;
if (!argsbody) {
argsbody = ListNode::create(PNK_ARGSBODY, parser);
if (!argsbody)
return false;
argsbody->setOp(JSOP_NOP);
argsbody->makeEmpty();
pn->pn_body = argsbody;
}
argsbody->append(argpn);
argpn->setOp(JSOP_GETARG);
@ -1286,17 +1279,24 @@ LeaveFunction(ParseNode *fn, Parser *parser, PropertyName *funName = NULL,
}
bool
Parser::functionArguments(ParseNode **listp, bool &hasDefaults, bool &hasRest)
Parser::functionArguments(ParseNode **listp, bool &hasRest)
{
if (tokenStream.getToken() != TOK_LP) {
reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_PAREN_BEFORE_FORMAL);
return false;
}
hasDefaults = false;
hasRest = false;
ParseNode *argsbody = ListNode::create(PNK_ARGSBODY, this);
if (!argsbody)
return false;
argsbody->setOp(JSOP_NOP);
argsbody->makeEmpty();
tc->sc->funbox->node->pn_body = argsbody;
if (!tokenStream.matchToken(TOK_RP)) {
bool hasDefaults = false;
#if JS_HAS_DESTRUCTURING
JSAtom *duplicatedArg = NULL;
bool destructuringArg = false;
@ -1430,7 +1430,10 @@ Parser::functionArguments(ParseNode **listp, bool &hasDefaults, bool &hasRest)
ParseNode *def_expr = assignExprWithoutYield(JSMSG_YIELD_IN_DEFAULT);
if (!def_expr)
return false;
tc->sc->funbox->node->pn_body->last()->pn_expr = def_expr;
ParseNode *arg = tc->sc->funbox->node->pn_body->last();
arg->pn_dflags |= PND_DEFAULT;
arg->pn_expr = def_expr;
tc->sc->funbox->ndefaults++;
} else if (!hasRest && hasDefaults) {
reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT);
return false;
@ -1595,12 +1598,12 @@ Parser::functionDef(HandlePropertyName funName, FunctionType type, FunctionSynta
/* Now parse formal argument list and compute fun->nargs. */
ParseNode *prelude = NULL;
bool hasRest, hasDefaults;
if (!functionArguments(&prelude, hasDefaults, hasRest))
bool hasRest;
if (!functionArguments(&prelude, hasRest))
return NULL;
fun->setArgCount(funsc.bindings.numArgs());
if (hasDefaults)
if (funbox->ndefaults)
fun->setHasDefaults();
if (hasRest)
fun->setHasRest();
@ -1758,12 +1761,8 @@ Parser::functionDef(HandlePropertyName funName, FunctionType type, FunctionSynta
pn->pn_funbox = funbox;
pn->setOp(op);
if (pn->pn_body) {
pn->pn_body->append(body);
pn->pn_body->pn_pos = body->pn_pos;
} else {
pn->pn_body = body;
}
JS_ASSERT_IF(!outertc->sc->inFunction && bodyLevel && kind == Statement,
pn->pn_cookie.isFree());

Просмотреть файл

@ -210,7 +210,7 @@ struct Parser : private AutoGCRooter
* Additional JS parsers.
*/
enum FunctionType { Getter, Setter, Normal };
bool functionArguments(ParseNode **list, bool &hasDefaults, bool &hasRest);
bool functionArguments(ParseNode **list, bool &hasRest);
ParseNode *functionDef(HandlePropertyName name, FunctionType type, FunctionSyntaxKind kind);

Просмотреть файл

@ -0,0 +1,22 @@
load(libdir + "asserts.js");
function f(a=42) {
return a;
function a() { return 19; }
}
assertEq(f()(), 19);
function h(a=b, b=43) {
return [a, b];
function b() { return 42; }
}
var res = h();
assertEq(res[0], res[1]);
assertEq(res[0](), 42);
assertThrowsInstanceOf(function i(b=FAIL) {
function b() {}
}, ReferenceError);
function j(a=(b=42), b=8) {
return b;
function b() {}
}
assertEq(j(), 42);

Просмотреть файл

@ -32,3 +32,13 @@ function f11(a=(0, c=1)) {}
assertEq(f11.length, 1);
var g = eval("(" + f11 + ")");
assertEq(g.length, 1);
function f12(a, b=4) {
return b;
function b() { }
}
assertEq(f12.toString(), "function f12(a, b = 4) {\n return b;\n\n function b() {\n }\n\n}");
function f13(a, b=4, ...rest) {
return b;
function b() { }
}
assertEq(f13.toString(), "function f13(a, b = 4, ...rest) {\n return b;\n\n function b() {\n }\n\n}");

Просмотреть файл

@ -0,0 +1,15 @@
assertEq(Number.isNaN(NaN), true);
assertEq(Number.isNaN(0/0), true);
assertEq(Number.isNaN(Number("NaN")), true);
assertEq(Number.isNaN(4), false);
assertEq(Number.isNaN(4.5), false);
assertEq(Number.isNaN("hi"), false);
assertEq(Number.isNaN("1.3"), false);
assertEq(Number.isNaN("51"), false);
assertEq(Number.isNaN(0), false);
assertEq(Number.isNaN(-0), false);
assertEq(Number.isNaN({valueOf: function () { return 3; }}), false);
assertEq(Number.isNaN({valueOf: function () { return 0/0; }}), false);
assertEq(Number.isNaN({ valueOf: function() { throw 17; } }), false);
assertEq(Number.isNaN({ toString: function() { throw 17; } }), false);
assertEq(Number.isNaN({ valueOf: function() { throw 17; }, toString: function() { throw 42; } }), false);

Просмотреть файл

@ -589,6 +589,7 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx)
case JSOP_HOLE:
case JSOP_LOOPHEAD:
case JSOP_LOOPENTRY:
case JSOP_ACTUALSFILLED:
break;
default:

Просмотреть файл

@ -1870,16 +1870,10 @@ STRING_TO_JSVAL(JSString *str)
return IMPL_TO_JSVAL(STRING_TO_JSVAL_IMPL(str));
}
static JS_ALWAYS_INLINE JSBool
JSVAL_IS_OBJECT(jsval v)
{
return JSVAL_IS_OBJECT_OR_NULL_IMPL(JSVAL_TO_IMPL(v));
}
static JS_ALWAYS_INLINE JSObject *
JSVAL_TO_OBJECT(jsval v)
{
JS_ASSERT(JSVAL_IS_OBJECT(v));
JS_ASSERT(JSVAL_IS_OBJECT_OR_NULL_IMPL(JSVAL_TO_IMPL(v)));
return JSVAL_TO_OBJECT_IMPL(JSVAL_TO_IMPL(v));
}

Просмотреть файл

@ -836,6 +836,27 @@ static JSFunctionSpec number_methods[] = {
JS_FS_END
};
// ES6 draft ES6 15.7.3.10
static JSBool
Number_isNaN(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1 || !args[0].isDouble()) {
args.rval().setBoolean(false);
return true;
}
args.rval().setBoolean(MOZ_DOUBLE_IS_NaN(args[0].toDouble()));
return true;
}
static JSFunctionSpec number_static_methods[] = {
JS_FN("isNaN", Number_isNaN, 1, 0),
JS_FS_END
};
/* NB: Keep this in synch with number_constants[]. */
enum nc_slot {
NC_NaN,
@ -998,6 +1019,9 @@ js_InitNumberClass(JSContext *cx, JSObject *obj)
if (!JS_DefineConstDoubles(cx, ctor, number_constants))
return NULL;
if (!DefinePropertiesAndBrand(cx, ctor, NULL, number_static_methods))
return NULL;
if (!DefinePropertiesAndBrand(cx, numberProto, NULL, number_methods))
return NULL;

Просмотреть файл

@ -2543,6 +2543,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, int nb)
JSString *str;
JSBool ok;
JSBool foreach;
JSBool defaultsSwitch = false;
#if JS_HAS_XML_SUPPORT
JSBool inXML, quoteAttr;
#else
@ -4835,6 +4836,19 @@ Decompile(SprintStack *ss, jsbytecode *pc, int nb)
int32_t j, n, low, high;
TableEntry *table, *tmp;
if (defaultsSwitch) {
defaultsSwitch = false;
len = GET_JUMP_OFFSET(pc);
if (jp->fun->hasRest()) {
// Jump over rest parameter things.
len += GetBytecodeLength(pc + len);
LOCAL_ASSERT(pc[len] == JSOP_POP);
len += GetBytecodeLength(pc + len);
}
todo = -2;
break;
}
sn = js_GetSrcNote(jp->script, pc);
LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_SWITCH);
len = js_GetSrcNoteOffset(sn, 0);
@ -5315,6 +5329,24 @@ Decompile(SprintStack *ss, jsbytecode *pc, int nb)
break;
#endif /* JS_HAS_XML_SUPPORT */
case JSOP_ACTUALSFILLED:
JS_ASSERT(!defaultsSwitch);
defaultsSwitch = true;
todo = -2;
break;
case JSOP_REST:
// Ignore bytecode related to handling rest.
pc += GetBytecodeLength(pc);
if (*pc == JSOP_UNDEFINED)
pc += GetBytecodeLength(pc);
LOCAL_ASSERT(*pc == JSOP_SETALIASEDVAR || *pc == JSOP_SETARG);
pc += GetBytecodeLength(pc);
LOCAL_ASSERT(*pc == JSOP_POP);
len = GetBytecodeLength(pc);
todo = -2;
break;
default:
todo = -2;
break;
@ -5539,6 +5571,24 @@ js_DecompileFunction(JSPrinter *jp)
uint16_t defstart = 0;
unsigned nformal = fun->nargs - fun->hasRest();
if (fun->hasDefaults()) {
jsbytecode *defpc;
for (defpc = pc; defpc < endpc; defpc += GetBytecodeLength(defpc)) {
if (*defpc == JSOP_ACTUALSFILLED)
break;
}
LOCAL_ASSERT_RV(defpc < endpc, JS_FALSE);
defpc += GetBytecodeLength(defpc);
LOCAL_ASSERT_RV(*defpc == JSOP_TABLESWITCH, JS_FALSE);
defbegin = defpc;
deflen = GET_JUMP_OFFSET(defpc);
defpc += JUMP_OFFSET_LEN;
defstart = GET_JUMP_OFFSET(defpc);
defpc += JUMP_OFFSET_LEN;
defpc += JUMP_OFFSET_LEN; // Skip high
deftable = defpc;
}
for (unsigned i = 0; i < fun->nargs; i++) {
if (i > 0)
js_puts(jp, ", ");
@ -5555,7 +5605,6 @@ js_DecompileFunction(JSPrinter *jp)
ptrdiff_t todo;
const char *lval;
JS_ASSERT(deflen == 0);
LOCAL_ASSERT(*pc == JSOP_GETARG || *pc == JSOP_GETALIASEDVAR);
pc += js_CodeSpec[*pc].length;
LOCAL_ASSERT(*pc == JSOP_DUP);
@ -5581,37 +5630,6 @@ js_DecompileFunction(JSPrinter *jp)
}
#endif
// Compute default parameters.
if ((*pc == JSOP_REST && pc[1] == JSOP_UNDEFINED) ||
*pc == JSOP_ACTUALSFILLED) {
#define SKIP(pc, op) LOCAL_ASSERT(*pc == op); pc += js_CodeSpec[op].length;
JS_ASSERT(fun->hasDefaults());
JS_ASSERT(deflen == 0);
if (fun->hasRest()) {
SKIP(pc, JSOP_REST);
SKIP(pc, JSOP_UNDEFINED);
JS_ASSERT(*pc == JSOP_SETARG || *pc == JSOP_SETALIASEDVAR);
pc += js_CodeSpec[*pc].length;
SKIP(pc, JSOP_POP);
}
SKIP(pc, JSOP_ACTUALSFILLED);
JS_ASSERT(*pc == JSOP_TABLESWITCH);
defbegin = pc;
deflen = GET_JUMP_OFFSET(pc);
pc += JUMP_OFFSET_LEN;
defstart = GET_JUMP_OFFSET(pc);
pc += JUMP_OFFSET_LEN;
pc += JUMP_OFFSET_LEN; // Skip high
deftable = pc;
pc = defbegin + deflen;
if (fun->hasRest()) {
SKIP(pc, JSOP_SETARG);
SKIP(pc, JSOP_POP);
}
#undef SKIP
}
#undef LOCAL_ASSERT
if (fun->hasDefaults() && deflen && i >= defstart && !isRest) {

Просмотреть файл

@ -2253,6 +2253,25 @@ mjit::Compiler::generateMethod()
frame.push(MagicValue(JS_OPTIMIZED_ARGUMENTS));
}
END_CASE(JSOP_ARGUMENTS)
BEGIN_CASE(JSOP_ACTUALSFILLED)
{
// We never inline things with defaults because of the switch.
JS_ASSERT(a == outer);
RegisterID value = frame.allocReg(), nactual = frame.allocReg();
int32_t defstart = GET_UINT16(PC);
masm.move(Imm32(defstart), value);
masm.load32(Address(JSFrameReg, StackFrame::offsetOfNumActual()), nactual);
// Best would be a single instruction where available (like
// cmovge on x86), but there's no way get that yet, so jump.
Jump j = masm.branch32(Assembler::LessThan, nactual, Imm32(defstart));
masm.move(nactual, value);
j.linkTo(masm.label(), &masm);
frame.freeReg(nactual);
frame.pushInt32(value);
}
END_CASE(JSOP_ACTUALSFILLED)
BEGIN_CASE(JSOP_ITERNEXT)
iterNext(GET_INT8(PC));

Просмотреть файл

@ -15,6 +15,7 @@ import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
import android.text.Spanned;
@ -569,7 +570,14 @@ public class AwesomeBar extends GeckoActivity implements GeckoEventListener {
break;
}
case R.id.remove_bookmark: {
(new GeckoAsyncTask<Void, Void, Void>() {
(new AsyncTask<Void, Void, Void>() {
private boolean mInReadingList;
@Override
public void onPreExecute() {
mInReadingList = mAwesomeTabs.isInReadingList();
}
@Override
public Void doInBackground(Void... params) {
BrowserDB.removeBookmark(mResolver, id);
@ -578,7 +586,11 @@ public class AwesomeBar extends GeckoActivity implements GeckoEventListener {
@Override
public void onPostExecute(Void result) {
Toast.makeText(AwesomeBar.this, R.string.bookmark_removed, Toast.LENGTH_SHORT).show();
int messageId = R.string.bookmark_removed;
if (mInReadingList)
messageId = R.string.reading_list_removed;
Toast.makeText(AwesomeBar.this, messageId, Toast.LENGTH_SHORT).show();
}
}).execute();
break;

Просмотреть файл

@ -78,6 +78,8 @@ public class AwesomeBarTabs extends TabHost {
private BookmarksListAdapter mBookmarksAdapter;
private SimpleExpandableListAdapter mHistoryAdapter;
private boolean mInReadingList;
// FIXME: This value should probably come from a
// prefs key (just like XUL-based fennec)
private static final int MAX_RESULTS = 100;
@ -184,6 +186,8 @@ public class AwesomeBarTabs extends TabHost {
mBookmarksQueryTask.cancel(false);
Pair<Integer, String> folderPair = mParentStack.getFirst();
mInReadingList = (folderPair.first == Bookmarks.FIXED_READING_LIST_ID);
mBookmarksQueryTask = new BookmarksQueryTask(folderPair.first, folderPair.second);
mBookmarksQueryTask.execute();
}
@ -241,6 +245,8 @@ public class AwesomeBarTabs extends TabHost {
return mResources.getString(R.string.bookmarks_folder_toolbar);
else if (guid.equals(Bookmarks.UNFILED_FOLDER_GUID))
return mResources.getString(R.string.bookmarks_folder_unfiled);
else if (guid.equals(Bookmarks.READING_LIST_FOLDER_GUID))
return mResources.getString(R.string.bookmarks_folder_reading_list);
// If for some reason we have a folder with a special GUID, but it's not one of
// the special folders we expect in the UI, just return the title from the DB.
@ -279,6 +285,15 @@ public class AwesomeBarTabs extends TabHost {
updateUrl(viewHolder.urlView, cursor);
updateFavicon(viewHolder.faviconView, cursor);
} else {
int guidIndex = cursor.getColumnIndexOrThrow(Bookmarks.GUID);
String guid = cursor.getString(guidIndex);
if (guid.equals(Bookmarks.READING_LIST_FOLDER_GUID)) {
viewHolder.faviconView.setImageResource(R.drawable.reading_list);
} else {
viewHolder.faviconView.setImageResource(R.drawable.folder);
}
viewHolder.titleView.setText(getFolderTitle(position));
}
@ -466,6 +481,7 @@ public class AwesomeBarTabs extends TabHost {
byte[] favicon = cursor.getBlob(cursor.getColumnIndexOrThrow(URLColumns.FAVICON));
Integer bookmarkId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID));
Integer historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID));
Integer display = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.DISPLAY));
// Use the URL instead of an empty title for consistency with the normal URL
// bar view - this is the equivalent of getDisplayTitle() in Tab.java
@ -719,6 +735,8 @@ public class AwesomeBarTabs extends TabHost {
mContentResolver = context.getContentResolver();
mContentObserver = null;
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mInReadingList = false;
}
@Override
@ -860,6 +878,12 @@ public class AwesomeBarTabs extends TabHost {
return imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private String getReaderForUrl(String url) {
// FIXME: still need to define the final way to open items from
// reading list. For now, we're using an about:reader page.
return "about:reader?url=" + url;
}
private void handleBookmarkItemClick(AdapterView<?> parent, View view, int position, long id) {
int headerCount = ((ListView) parent).getHeaderViewsCount();
// If we tap on the header view, there's nothing to do
@ -885,9 +909,14 @@ public class AwesomeBarTabs extends TabHost {
// Otherwise, just open the URL
String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
if (mUrlOpenListener != null)
if (mUrlOpenListener != null) {
if (mInReadingList) {
url = getReaderForUrl(url);
}
mUrlOpenListener.onUrlOpen(url);
}
}
private void handleHistoryItemClick(int groupPosition, int childPosition) {
@SuppressWarnings("unchecked")
@ -908,8 +937,14 @@ public class AwesomeBarTabs extends TabHost {
if (item instanceof Cursor) {
Cursor cursor = (Cursor) item;
String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
if (mUrlOpenListener != null)
if (mUrlOpenListener != null) {
int display = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.DISPLAY));
if (display == Combined.DISPLAY_READER) {
url = getReaderForUrl(url);
}
mUrlOpenListener.onUrlOpen(url);
}
} else {
if (mUrlOpenListener != null)
mUrlOpenListener.onSearch((String)item);
@ -1003,6 +1038,10 @@ public class AwesomeBarTabs extends TabHost {
});
}
public boolean isInReadingList() {
return mInReadingList;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
hideSoftInput(this);

Просмотреть файл

@ -12,6 +12,7 @@ import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
@ -24,18 +25,21 @@ import android.view.animation.TranslateAnimation;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.view.ViewConfiguration;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.RelativeLayout.LayoutParams;
import android.widget.TextView;
import android.widget.TextSwitcher;
import android.widget.Toast;
import android.widget.ViewSwitcher;
public class BrowserToolbar implements ViewSwitcher.ViewFactory,
@ -49,6 +53,7 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
public ImageButton mFavicon;
public ImageButton mStop;
public ImageButton mSiteSecurity;
public ImageButton mReader;
private AnimationDrawable mProgressSpinner;
private TextSwitcher mTabsCount;
private ImageView mShadow;
@ -60,9 +65,13 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
private LayoutInflater mInflater;
private Handler mHandler;
private int[] mPadding;
private boolean mTitleCanExpand;
private boolean mHasSoftMenuButton;
private boolean mShowSiteSecurity;
private boolean mShowReader;
private ReaderPopup mReaderPopup;
private static List<View> sActionItems;
private int mDuration;
@ -82,7 +91,10 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
public void from(LinearLayout layout) {
mLayout = layout;
mTitleCanExpand = true;
mShowSiteSecurity = false;
mShowReader = false;
mReaderPopup = null;
mAwesomeBar = (Button) mLayout.findViewById(R.id.awesome_bar);
mAwesomeBar.setOnClickListener(new Button.OnClickListener() {
@ -152,6 +164,16 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
}
});
mReader = (ImageButton) mLayout.findViewById(R.id.reader);
mReader.setOnClickListener(new Button.OnClickListener() {
public void onClick(View view) {
if (mReaderPopup == null)
mReaderPopup = new ReaderPopup(GeckoApp.mAppContext);
mReaderPopup.show();
}
});
mShadow = (ImageView) mLayout.findViewById(R.id.shadow);
mHandler = new Handler();
@ -304,8 +326,11 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
public void setStopVisibility(boolean visible) {
mStop.setVisibility(visible ? View.VISIBLE : View.GONE);
mSiteSecurity.setVisibility(visible ? View.GONE : View.VISIBLE);
if (!visible && mTitleCanExpand)
mSiteSecurity.setVisibility(mShowSiteSecurity && !visible ? View.VISIBLE : View.GONE);
mReader.setVisibility(mShowReader && !visible ? View.VISIBLE : View.GONE);
if (!visible && !mShowSiteSecurity && !mShowReader)
mAwesomeBar.setPadding(mPadding[0], mPadding[1], mPadding[2], mPadding[3]);
else
mAwesomeBar.setPadding(mPadding[0], mPadding[1], mPadding[0], mPadding[3]);
@ -342,7 +367,7 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
}
public void setSecurityMode(String mode) {
mTitleCanExpand = false;
mShowSiteSecurity = true;
if (mode.equals(SiteIdentityPopup.IDENTIFIED)) {
mSiteSecurity.setImageLevel(1);
@ -350,10 +375,14 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
mSiteSecurity.setImageLevel(2);
} else {
mSiteSecurity.setImageLevel(0);
mTitleCanExpand = true;
mShowSiteSecurity = false;
}
}
public void setReaderVisibility(boolean showReader) {
mShowReader = showReader;
}
public void setVisibility(int visibility) {
mLayout.setVisibility(visibility);
}
@ -418,6 +447,7 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
setTitle(tab.getDisplayTitle());
setFavicon(tab.getFavicon());
setSecurityMode(tab.getSecurityMode());
setReaderVisibility(tab.getReaderEnabled());
setProgressVisibility(tab.getState() == Tab.STATE_LOADING);
setShadowVisibility((url == null) || !url.startsWith("about:"));
updateTabCount(Tabs.getInstance().getCount());
@ -496,4 +526,54 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
mArrow.setLayoutParams(newParams);
}
}
public class ReaderPopup extends PopupWindow {
private int mWidth;
private ReaderPopup(Context context) {
super(context);
setBackgroundDrawable(new BitmapDrawable());
setOutsideTouchable(true);
setWindowLayoutMode(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
LayoutInflater inflater = LayoutInflater.from(context);
FrameLayout layout = (FrameLayout) inflater.inflate(R.layout.reader_popup, null);
setContentView(layout);
layout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
mWidth = layout.getMeasuredWidth();
Button readingListButton = (Button) layout.findViewById(R.id.reading_list);
readingListButton.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
Tab selectedTab = Tabs.getInstance().getSelectedTab();
if (selectedTab != null) {
selectedTab.addToReadingList();
}
Toast.makeText(GeckoApp.mAppContext,
R.string.reading_list_added, Toast.LENGTH_SHORT).show();
dismiss();
}
});
Button readerModeButton = (Button) layout.findViewById(R.id.reader_mode);
readerModeButton.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
Tab selectedTab = Tabs.getInstance().getSelectedTab();
if (selectedTab != null) {
selectedTab.readerMode();
}
dismiss();
}
});
}
public void show() {
showAsDropDown(mReader, (mReader.getWidth() - mWidth) / 2, 0);
}
}
}

Просмотреть файл

@ -788,6 +788,7 @@ abstract public class GeckoApp
tab.setContentType(contentType);
tab.clearFavicon();
tab.updateIdentityData(null);
tab.setReaderEnabled(false);
tab.removeTransientDoorHangers();
tab.setAllowZoom(true);
tab.setDefaultZoom(0);
@ -827,6 +828,21 @@ abstract public class GeckoApp
});
}
void handleReaderEnabled(final int tabId) {
final Tab tab = Tabs.getInstance().getTab(tabId);
if (tab == null)
return;
tab.setReaderEnabled(true);
mMainHandler.post(new Runnable() {
public void run() {
if (Tabs.getInstance().isSelectedTab(tab))
mBrowserToolbar.setReaderVisibility(tab.getReaderEnabled());
}
});
}
void handleLoadError(final int tabId, final String uri, final String title) {
final Tab tab = Tabs.getInstance().getTab(tabId);
if (tab == null)
@ -1005,6 +1021,9 @@ abstract public class GeckoApp
final JSONObject identity = message.getJSONObject("identity");
Log.i(LOGTAG, "Security Mode - " + identity.getString("mode"));
handleSecurityChange(tabId, identity);
} else if (event.equals("Content:ReaderEnabled")) {
final int tabId = message.getInt("tabID");
handleReaderEnabled(tabId);
} else if (event.equals("Content:StateChange")) {
final int tabId = message.getInt("tabID");
final String uri = message.getString("uri");
@ -1363,12 +1382,14 @@ abstract public class GeckoApp
tab.setState("about:home".equals(uri) ? Tab.STATE_SUCCESS : Tab.STATE_LOADING);
tab.updateIdentityData(null);
tab.setReaderEnabled(false);
if (Tabs.getInstance().isSelectedTab(tab))
getLayerController().getView().getRenderer().resetCheckerboard();
mMainHandler.post(new Runnable() {
public void run() {
if (Tabs.getInstance().isSelectedTab(tab)) {
mBrowserToolbar.setSecurityMode(tab.getSecurityMode());
mBrowserToolbar.setReaderVisibility(tab.getReaderEnabled());
mBrowserToolbar.updateBackButton(tab.canDoBack());
mBrowserToolbar.updateForwardButton(tab.canDoForward());
invalidateOptionsMenu();
@ -1909,6 +1930,7 @@ abstract public class GeckoApp
GeckoAppShell.registerGeckoEventListener("log", GeckoApp.mAppContext);
GeckoAppShell.registerGeckoEventListener("Content:LocationChange", GeckoApp.mAppContext);
GeckoAppShell.registerGeckoEventListener("Content:SecurityChange", GeckoApp.mAppContext);
GeckoAppShell.registerGeckoEventListener("Content:ReaderEnabled", GeckoApp.mAppContext);
GeckoAppShell.registerGeckoEventListener("Content:StateChange", GeckoApp.mAppContext);
GeckoAppShell.registerGeckoEventListener("Content:LoadError", GeckoApp.mAppContext);
GeckoAppShell.registerGeckoEventListener("Content:PageShow", GeckoApp.mAppContext);
@ -2260,6 +2282,7 @@ abstract public class GeckoApp
GeckoAppShell.unregisterGeckoEventListener("log", GeckoApp.mAppContext);
GeckoAppShell.unregisterGeckoEventListener("Content:LocationChange", GeckoApp.mAppContext);
GeckoAppShell.unregisterGeckoEventListener("Content:SecurityChange", GeckoApp.mAppContext);
GeckoAppShell.unregisterGeckoEventListener("Content:ReaderEnabled", GeckoApp.mAppContext);
GeckoAppShell.unregisterGeckoEventListener("Content:StateChange", GeckoApp.mAppContext);
GeckoAppShell.unregisterGeckoEventListener("Content:LoadError", GeckoApp.mAppContext);
GeckoAppShell.unregisterGeckoEventListener("Content:PageShow", GeckoApp.mAppContext);

Просмотреть файл

@ -272,6 +272,7 @@ RES_LAYOUT = \
res/layout/site_setting_title.xml \
res/layout/setup_screen.xml \
res/layout/site_identity_popup.xml \
res/layout/reader_popup.xml \
res/layout/remote_tabs.xml \
res/layout/remote_tabs_child.xml \
res/layout/remote_tabs_group.xml \
@ -406,6 +407,8 @@ RES_DRAWABLE_BASE = \
res/drawable/site_security_identified.png \
res/drawable/site_security_verified.png \
res/drawable/urlbar_stop.png \
res/drawable/reader.png \
res/drawable/reading_list.png \
res/drawable/validation_arrow.png \
res/drawable/validation_arrow_inverted.png \
res/drawable/validation_bg.9.png \
@ -472,6 +475,8 @@ RES_DRAWABLE_HDPI = \
res/drawable-hdpi/site_security_identified.png \
res/drawable-hdpi/site_security_verified.png \
res/drawable-hdpi/urlbar_stop.png \
res/drawable-hdpi/reader.png \
res/drawable-hdpi/reading_list.png \
res/drawable-hdpi/validation_arrow.png \
res/drawable-hdpi/validation_arrow_inverted.png \
res/drawable-hdpi/validation_bg.9.png \
@ -571,6 +576,8 @@ RES_DRAWABLE_XHDPI_V11 = \
res/drawable-xhdpi-v11/find_next.png \
res/drawable-xhdpi-v11/find_prev.png \
res/drawable-xhdpi-v11/urlbar_stop.png \
res/drawable-xhdpi-v11/reader.png \
res/drawable-xhdpi-v11/reading_list.png \
res/drawable-xhdpi-v11/larry_blue.png \
res/drawable-xhdpi-v11/larry_green.png \
res/drawable-xhdpi-v11/menu.png \

Просмотреть файл

@ -43,6 +43,7 @@ public final class Tab {
private String mFaviconUrl;
private int mFaviconSize;
private JSONObject mIdentityData;
private boolean mReaderEnabled;
private Drawable mThumbnail;
private int mHistoryIndex;
private int mHistorySize;
@ -80,6 +81,7 @@ public final class Tab {
mFaviconUrl = null;
mFaviconSize = 0;
mIdentityData = null;
mReaderEnabled = false;
mThumbnail = null;
mHistoryIndex = -1;
mHistorySize = 0;
@ -199,6 +201,10 @@ public final class Tab {
return mIdentityData;
}
public boolean getReaderEnabled() {
return mReaderEnabled;
}
public boolean isBookmark() {
return mBookmark;
}
@ -333,6 +339,10 @@ public final class Tab {
mIdentityData = identityData;
}
public void setReaderEnabled(boolean readerEnabled) {
mReaderEnabled = readerEnabled;
}
private void updateBookmark() {
final String url = getURL();
if (url == null)
@ -372,6 +382,28 @@ public final class Tab {
});
}
public void addToReadingList() {
if (!mReaderEnabled)
return;
GeckoAppShell.getHandler().post(new Runnable() {
public void run() {
String url = getURL();
if (url == null)
return;
BrowserDB.addReadingListItem(mContentResolver, getTitle(), url);
}
});
}
public void readerMode() {
if (!mReaderEnabled)
return;
// Do nothing for now
}
public boolean doReload() {
GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", "");
GeckoAppShell.sendEventToGecko(e);

Просмотреть файл

@ -266,6 +266,10 @@ public class AndroidBrowserDB implements BrowserDB.BrowserDBIface {
removeBookmarkPre11(cr, uri);
}
public void addReadingListItem(ContentResolver cr, String title, String uri) {
// Do nothing
}
public void registerBookmarkObserverPre11(ContentResolver cr, ContentObserver observer) {
cr.registerContentObserver(Browser.BOOKMARKS_URI, false, observer);
}

Просмотреть файл

@ -76,6 +76,7 @@ public class BrowserContract {
public static final int FIXED_ROOT_ID = 0;
public static final int FAKE_DESKTOP_FOLDER_ID = -1;
public static final int FIXED_READING_LIST_ID = -2;
public static final String MOBILE_FOLDER_GUID = "mobile";
public static final String PLACES_FOLDER_GUID = "places";
@ -83,6 +84,7 @@ public class BrowserContract {
public static final String TAGS_FOLDER_GUID = "tags";
public static final String TOOLBAR_FOLDER_GUID = "toolbar";
public static final String UNFILED_FOLDER_GUID = "unfiled";
public static final String READING_LIST_FOLDER_GUID = "readinglist";
public static final String FAKE_DESKTOP_FOLDER_GUID = "desktop";
public static final int TYPE_FOLDER = 0;
@ -118,8 +120,12 @@ public class BrowserContract {
private Combined() {}
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "combined");
public static final int DISPLAY_NORMAL = 0;
public static final int DISPLAY_READER = 1;
public static final String BOOKMARK_ID = "bookmark_id";
public static final String HISTORY_ID = "history_id";
public static final String DISPLAY = "display";
}
public static final class Schema {

Просмотреть файл

@ -61,6 +61,8 @@ public class BrowserDB {
public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword);
public void addReadingListItem(ContentResolver cr, String title, String uri);
public BitmapDrawable getFaviconForUrl(ContentResolver cr, String uri);
public void updateFaviconForUrl(ContentResolver cr, String uri, BitmapDrawable favicon);
@ -148,6 +150,10 @@ public class BrowserDB {
sDb.updateBookmark(cr, id, uri, title, keyword);
}
public static void addReadingListItem(ContentResolver cr, String title, String uri) {
sDb.addReadingListItem(cr, title, uri);
}
public static BitmapDrawable getFaviconForUrl(ContentResolver cr, String uri) {
return sDb.getFaviconForUrl(cr, uri);
}

Просмотреть файл

@ -67,7 +67,7 @@ public class BrowserProvider extends ContentProvider {
static final String DATABASE_NAME = "browser.db";
static final int DATABASE_VERSION = 8;
static final int DATABASE_VERSION = 9;
// Maximum age of deleted records to be cleaned up (20 days in ms)
static final long MAX_AGE_OF_DELETED_RECORDS = 86400000 * 20;
@ -209,6 +209,7 @@ public class BrowserProvider extends ContentProvider {
map.put(Combined._ID, Combined._ID);
map.put(Combined.BOOKMARK_ID, Combined.BOOKMARK_ID);
map.put(Combined.HISTORY_ID, Combined.HISTORY_ID);
map.put(Combined.DISPLAY, Combined.DISPLAY);
map.put(Combined.URL, Combined.URL);
map.put(Combined.TITLE, Combined.TITLE);
map.put(Combined.VISITS, Combined.VISITS);
@ -435,6 +436,64 @@ public class BrowserProvider extends ContentProvider {
" ON " + Combined.URL + " = " + qualifyColumn(TABLE_IMAGES, Images.URL));
}
private void createCombinedWithImagesViewOn9(SQLiteDatabase db) {
debug("Creating " + VIEW_COMBINED_WITH_IMAGES + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_IMAGES + " AS" +
" SELECT " + Combined.BOOKMARK_ID + ", " +
Combined.HISTORY_ID + ", " +
// We need to return an _id column because CursorAdapter requires it for its
// default implementation for the getItemId() method. However, since
// we're not using this feature in the parts of the UI using this view,
// we can just use 0 for all rows.
"0 AS " + Combined._ID + ", " +
Combined.URL + ", " +
Combined.TITLE + ", " +
Combined.VISITS + ", " +
Combined.DISPLAY + ", " +
Combined.DATE_LAST_VISITED + ", " +
qualifyColumn(TABLE_IMAGES, Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
qualifyColumn(TABLE_IMAGES, Images.THUMBNAIL) + " AS " + Combined.THUMBNAIL +
" FROM (" +
// Bookmarks without history.
" SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
"-1 AS " + Combined.HISTORY_ID + ", " +
"-1 AS " + Combined.VISITS + ", " +
"-1 AS " + Combined.DATE_LAST_VISITED +
" FROM " + TABLE_BOOKMARKS +
" WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0 AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
" NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
" UNION ALL" +
// History with and without bookmark.
" SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
// Prioritze bookmark titles over history titles, since the user may have
// customized the title for a bookmark.
"COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
" FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
" ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
" WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
qualifyColumn(TABLE_HISTORY, History.IS_DELETED) + " = 0 AND (" +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + ")" +
") LEFT OUTER JOIN " + TABLE_IMAGES +
" ON " + Combined.URL + " = " + qualifyColumn(TABLE_IMAGES, Images.URL));
}
@Override
public void onCreate(SQLiteDatabase db) {
debug("Creating browser.db: " + db.getPath());
@ -445,7 +504,7 @@ public class BrowserProvider extends ContentProvider {
createBookmarksWithImagesView(db);
createHistoryWithImagesView(db);
createCombinedWithImagesView(db);
createCombinedWithImagesViewOn9(db);
createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID,
R.string.bookmarks_folder_places, 0);
@ -539,6 +598,8 @@ public class BrowserProvider extends ContentProvider {
R.string.bookmarks_folder_tags, 3);
createOrUpdateSpecialFolder(db, Bookmarks.UNFILED_FOLDER_GUID,
R.string.bookmarks_folder_unfiled, 4);
createOrUpdateSpecialFolder(db, Bookmarks.READING_LIST_FOLDER_GUID,
R.string.bookmarks_folder_reading_list, 5);
}
private void createOrUpdateSpecialFolder(SQLiteDatabase db,
@ -550,6 +611,8 @@ public class BrowserProvider extends ContentProvider {
if (guid.equals(Bookmarks.PLACES_FOLDER_GUID))
values.put(Bookmarks._ID, Bookmarks.FIXED_ROOT_ID);
else if (guid.equals(Bookmarks.READING_LIST_FOLDER_GUID))
values.put(Bookmarks._ID, Bookmarks.FIXED_READING_LIST_ID);
// Set the parent to 0, which sync assumes is the root
values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID);
@ -845,6 +908,16 @@ public class BrowserProvider extends ContentProvider {
db.execSQL("DROP TABLE " + TABLE_DUPES);
}
private void upgradeDatabaseFrom8to9(SQLiteDatabase db) {
createOrUpdateSpecialFolder(db, Bookmarks.READING_LIST_FOLDER_GUID,
R.string.bookmarks_folder_reading_list, 5);
debug("Dropping view: " + VIEW_COMBINED_WITH_IMAGES);
db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_IMAGES);
createCombinedWithImagesViewOn9(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
debug("Upgrading browser.db: " + db.getPath() + " from " +
@ -882,6 +955,9 @@ public class BrowserProvider extends ContentProvider {
case 8:
upgradeDatabaseFrom7to8(db);
case 9:
upgradeDatabaseFrom8to9(db);
}
}

Просмотреть файл

@ -47,6 +47,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
// Use wrapped Boolean so that we can have a null state
private Boolean mDesktopBookmarksExist;
private Boolean mReadingListItemsExist;
private final Uri mBookmarksUriWithProfile;
private final Uri mParentsUriWithProfile;
@ -70,6 +71,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
mProfile = profile;
mFolderIdMap = new HashMap<String, Long>();
mDesktopBookmarksExist = null;
mReadingListItemsExist = null;
mBookmarksUriWithProfile = appendProfile(Bookmarks.CONTENT_URI);
mParentsUriWithProfile = appendProfile(Bookmarks.PARENTS_CONTENT_URI);
@ -88,6 +90,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
// Invalidate cached data
public void invalidateCachedState() {
mDesktopBookmarksExist = null;
mReadingListItemsExist = null;
}
private Uri historyUriWithLimit(int limit) {
@ -156,6 +159,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
Combined.URL,
Combined.TITLE,
Combined.FAVICON,
Combined.DISPLAY,
Combined.BOOKMARK_ID,
Combined.HISTORY_ID },
constraint,
@ -271,6 +275,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
Cursor c = null;
boolean addDesktopFolder = false;
boolean addReadingListFolder = false;
// We always want to show mobile bookmarks in the root view.
if (folderId == Bookmarks.FIXED_ROOT_ID) {
@ -279,6 +284,10 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
// We'll add a fake "Desktop Bookmarks" folder to the root view if desktop
// bookmarks exist, so that the user can still access non-mobile bookmarks.
addDesktopFolder = desktopBookmarksExist(cr);
// We'll add the Reading List folder to the root view if any reading
// list items exist.
addReadingListFolder = readingListItemsExist(cr);
}
if (folderId == Bookmarks.FAKE_DESKTOP_FOLDER_ID) {
@ -306,9 +315,9 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
null);
}
if (addDesktopFolder) {
// Wrap cursor to add fake desktop bookmarks folder
c = new DesktopBookmarksCursorWrapper(c);
if (addDesktopFolder || addReadingListFolder) {
// Wrap cursor to add fake desktop bookmarks and reading list folders
c = new SpecialFoldersCursorWrapper(c, addDesktopFolder, addReadingListFolder);
}
return new LocalDBCursor(c);
@ -340,15 +349,40 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
}
// Cache result for future queries
mDesktopBookmarksExist = (count == 1);
mDesktopBookmarksExist = (count > 0);
return mDesktopBookmarksExist;
}
private boolean readingListItemsExist(ContentResolver cr) {
if (mReadingListItemsExist != null)
return mReadingListItemsExist;
Cursor c = null;
int count = 0;
try {
c = cr.query(bookmarksUriWithLimit(1),
new String[] { Bookmarks._ID },
Bookmarks.PARENT + " = ?",
new String[] { String.valueOf(Bookmarks.FIXED_READING_LIST_ID) },
null);
count = c.getCount();
} finally {
c.close();
}
// Cache result for future queries
mReadingListItemsExist = (count > 0);
return mReadingListItemsExist;
}
public boolean isBookmark(ContentResolver cr, String uri) {
// This method is about normal bookmarks, not the Reading List
Cursor cursor = cr.query(mBookmarksUriWithProfile,
new String[] { Bookmarks._ID },
Bookmarks.URL + " = ?",
new String[] { uri },
Bookmarks.URL + " = ? AND " +
Bookmarks.PARENT + " != ?",
new String[] { uri,
String.valueOf(Bookmarks.FIXED_READING_LIST_ID) },
Bookmarks.URL);
int count = cursor.getCount();
@ -414,11 +448,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
debug("Updated " + updated + " rows to new modified time.");
}
public void addBookmark(ContentResolver cr, String title, String uri) {
long folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
if (folderId < 0)
return;
private void addBookmarkItem(ContentResolver cr, String title, String uri, long folderId) {
final long now = System.currentTimeMillis();
ContentValues values = new ContentValues();
values.put(Browser.BookmarkColumns.TITLE, title);
@ -429,14 +459,14 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
// Restore deleted record if possible
values.put(Bookmarks.IS_DELETED, 0);
Uri contentUri = mBookmarksUriWithProfile;
int updated = cr.update(contentUri,
int updated = cr.update(mBookmarksUriWithProfile,
values,
Bookmarks.URL + " = ?",
new String[] { uri });
Bookmarks.URL + " = ? AND " +
Bookmarks.PARENT + " = ?",
new String[] { uri, String.valueOf(folderId) });
if (updated == 0)
cr.insert(contentUri, values);
cr.insert(mBookmarksUriWithProfile, values);
// Bump parent modified time using its ID.
debug("Bumping parent modified time for addition to: " + folderId);
@ -446,10 +476,15 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
ContentValues bumped = new ContentValues();
bumped.put(Bookmarks.DATE_MODIFIED, now);
updated = cr.update(contentUri, bumped, where, args);
updated = cr.update(mBookmarksUriWithProfile, bumped, where, args);
debug("Updated " + updated + " rows to new modified time.");
}
public void addBookmark(ContentResolver cr, String title, String uri) {
long folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
addBookmarkItem(cr, title, uri, folderId);
}
public void removeBookmark(ContentResolver cr, int id) {
Uri contentUri = mBookmarksUriWithProfile;
@ -468,11 +503,17 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
// Do this now so that the items still exist!
bumpParents(cr, Bookmarks.URL, uri);
final String[] urlArgs = new String[] { uri };
final String urlEquals = Bookmarks.URL + " = ?";
// Toggling bookmark on an URL should not affect the items in the reading list
final String[] urlArgs = new String[] { uri, String.valueOf(Bookmarks.FIXED_READING_LIST_ID) };
final String urlEquals = Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " != ?";
cr.delete(contentUri, urlEquals, urlArgs);
}
public void addReadingListItem(ContentResolver cr, String title, String uri) {
addBookmarkItem(cr, title, uri, Bookmarks.FIXED_READING_LIST_ID);
}
public void registerBookmarkObserver(ContentResolver cr, ContentObserver observer) {
cr.registerContentObserver(mBookmarksUriWithProfile, false, observer);
}
@ -587,47 +628,72 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
// This wrapper adds a fake "Desktop Bookmarks" folder entry to the
// beginning of the cursor's data set.
private static class DesktopBookmarksCursorWrapper extends CursorWrapper {
private boolean mAtDesktopBookmarksPosition = false;
private class SpecialFoldersCursorWrapper extends CursorWrapper {
private int mIndexOffset;
public DesktopBookmarksCursorWrapper(Cursor c) {
private int mDesktopBookmarksIndex = -1;
private int mReadingListIndex = -1;
private boolean mAtDesktopBookmarksPosition = false;
private boolean mAtReadingListPosition = false;
public SpecialFoldersCursorWrapper(Cursor c, boolean showDesktopBookmarks, boolean showReadingList) {
super(c);
mIndexOffset = 0;
if (showDesktopBookmarks) {
mDesktopBookmarksIndex = mIndexOffset;
mIndexOffset++;
}
if (showReadingList) {
mReadingListIndex = mIndexOffset;
mIndexOffset++;
}
}
@Override
public int getCount() {
return super.getCount() + 1;
return super.getCount() + mIndexOffset;
}
@Override
public boolean moveToPosition(int position) {
if (position == 0) {
mAtDesktopBookmarksPosition = true;
return true;
}
mAtDesktopBookmarksPosition = (mDesktopBookmarksIndex == position);
mAtReadingListPosition = (mReadingListIndex == position);
mAtDesktopBookmarksPosition = false;
return super.moveToPosition(position - 1);
if (mAtDesktopBookmarksPosition || mAtReadingListPosition)
return true;
return super.moveToPosition(position - mIndexOffset);
}
@Override
public long getLong(int columnIndex) {
if (!mAtDesktopBookmarksPosition)
if (!mAtDesktopBookmarksPosition && !mAtReadingListPosition)
return super.getLong(columnIndex);
if (columnIndex == getColumnIndex(Bookmarks._ID))
return Bookmarks.FAKE_DESKTOP_FOLDER_ID;
if (columnIndex == getColumnIndex(Bookmarks.PARENT))
if (columnIndex == getColumnIndex(Bookmarks.PARENT)) {
return Bookmarks.FIXED_ROOT_ID;
}
return -1;
}
@Override
public int getInt(int columnIndex) {
if (!mAtDesktopBookmarksPosition)
if (!mAtDesktopBookmarksPosition && !mAtReadingListPosition)
return super.getInt(columnIndex);
if (columnIndex == getColumnIndex(Bookmarks._ID)) {
if (mAtDesktopBookmarksPosition) {
return Bookmarks.FAKE_DESKTOP_FOLDER_ID;
} else if (mAtReadingListPosition) {
return Bookmarks.FIXED_READING_LIST_ID;
}
}
if (columnIndex == getColumnIndex(Bookmarks.TYPE))
return Bookmarks.TYPE_FOLDER;
@ -636,11 +702,16 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
@Override
public String getString(int columnIndex) {
if (!mAtDesktopBookmarksPosition)
if (!mAtDesktopBookmarksPosition && !mAtReadingListPosition)
return super.getString(columnIndex);
if (columnIndex == getColumnIndex(Bookmarks.GUID))
if (columnIndex == getColumnIndex(Bookmarks.GUID)) {
if (mAtDesktopBookmarksPosition) {
return Bookmarks.FAKE_DESKTOP_FOLDER_GUID;
} else if (mAtReadingListPosition) {
return Bookmarks.READING_LIST_FOLDER_GUID;
}
}
return "";
}

Просмотреть файл

@ -124,6 +124,11 @@
<!ENTITY site_settings_clear "Clear">
<!ENTITY site_settings_no_settings "There are no settings to clear.">
<!ENTITY add_to_reading_list "Add to Reading List">
<!ENTITY reading_list_added "Page added to your Reading List">
<!ENTITY reading_list_removed "Page removed from your Reading List">
<!ENTITY reader_mode "Reader Mode">
<!ENTITY masterpassword_create_title "Create Master Password">
<!ENTITY masterpassword_remove_title "Remove Master Password">
<!ENTITY masterpassword_password "Password">

Просмотреть файл

@ -72,6 +72,7 @@
<!ENTITY bookmarks.folder.unfiled.label 'Unsorted Bookmarks'>
<!ENTITY bookmarks.folder.desktop.label 'Desktop Bookmarks'>
<!ENTITY bookmarks.folder.mobile.label 'Mobile Bookmarks'>
<!ENTITY bookmarks.folder.readinglist.label 'Reading List'>
<!-- Notification strings -->
<!ENTITY sync.notification.oneaccount.label 'Only one &syncBrand.fullName.label; account is supported.'>

Двоичные данные
mobile/android/base/resources/drawable-hdpi/reader.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.6 KiB

Двоичные данные
mobile/android/base/resources/drawable-hdpi/reading_list.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.1 KiB

Двоичные данные
mobile/android/base/resources/drawable-xhdpi-v11/reader.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.9 KiB

Двоичные данные
mobile/android/base/resources/drawable-xhdpi-v11/reading_list.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.4 KiB

Двоичные данные
mobile/android/base/resources/drawable/reader.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.3 KiB

Двоичные данные
mobile/android/base/resources/drawable/reading_list.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.7 KiB

Просмотреть файл

@ -86,24 +86,28 @@
android:src="@drawable/favicon"
android:layout_alignLeft="@id/awesome_bar"/>
<ImageButton android:id="@+id/stop"
style="@style/AddressBar.ImageButton"
android:layout_width="20dip"
android:layout_height="20dip"
android:layout_marginRight="7dip"
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dip"
android:layout_centerVertical="true"
android:src="@drawable/urlbar_stop"
android:layout_alignRight="@id/awesome_bar"
android:visibility="gone"/>
android:orientation="horizontal">
<ImageButton android:id="@+id/site_security"
style="@style/AddressBar.ImageButton"
android:layout_width="20dip"
android:layout_height="20dip"
android:layout_marginRight="7dip"
android:layout_centerVertical="true"
android:layout_width="24dip"
android:layout_height="24dip"
android:src="@drawable/site_security_level"
android:layout_alignRight="@id/awesome_bar"/>
android:visibility="gone"/>
<ImageButton android:id="@+id/stop"
style="@style/AddressBar.ImageButton"
android:layout_width="24dip"
android:layout_height="24dip"
android:src="@drawable/urlbar_stop"
android:visibility="gone"/>
</LinearLayout>
<ImageView android:id="@+id/shadow"
android:layout_width="fill_parent"

Просмотреть файл

@ -96,24 +96,28 @@
android:src="@drawable/favicon"
android:layout_toRightOf="@id/forward"/>
<ImageButton android:id="@+id/stop"
style="@style/AddressBar.ImageButton"
android:layout_width="28dip"
android:layout_height="28dip"
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dip"
android:layout_centerVertical="true"
android:src="@drawable/urlbar_stop"
android:layout_alignRight="@id/awesome_bar"
android:visibility="gone"/>
android:orientation="horizontal">
<ImageButton android:id="@+id/site_security"
style="@style/AddressBar.ImageButton"
android:layout_width="24dip"
android:layout_height="24dip"
android:layout_marginRight="10dip"
android:layout_centerVertical="true"
android:src="@drawable/site_security_level"
android:layout_alignRight="@id/awesome_bar"/>
android:visibility="gone"/>
<ImageButton android:id="@+id/stop"
style="@style/AddressBar.ImageButton"
android:layout_width="24dip"
android:layout_height="24dip"
android:src="@drawable/urlbar_stop"
android:visibility="gone"/>
</LinearLayout>
<ImageView android:id="@+id/shadow"
android:layout_width="fill_parent"

Просмотреть файл

@ -96,24 +96,28 @@
android:src="@drawable/favicon"
android:layout_toRightOf="@id/forward"/>
<ImageButton android:id="@+id/stop"
style="@style/AddressBar.ImageButton"
android:layout_width="28dip"
android:layout_height="28dip"
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dip"
android:layout_centerVertical="true"
android:src="@drawable/urlbar_stop"
android:layout_alignRight="@id/awesome_bar"
android:visibility="gone"/>
android:orientation="horizontal">
<ImageButton android:id="@+id/site_security"
style="@style/AddressBar.ImageButton"
android:layout_width="24dip"
android:layout_height="24dip"
android:layout_marginRight="10dip"
android:layout_centerVertical="true"
android:src="@drawable/site_security_level"
android:layout_alignRight="@id/awesome_bar"/>
android:visibility="gone"/>
<ImageButton android:id="@+id/stop"
style="@style/AddressBar.ImageButton"
android:layout_width="24dip"
android:layout_height="24dip"
android:src="@drawable/urlbar_stop"
android:visibility="gone"/>
</LinearLayout>
<ImageView android:id="@+id/shadow"
android:layout_width="fill_parent"

Просмотреть файл

@ -86,24 +86,35 @@
android:src="@drawable/favicon"
android:layout_alignLeft="@id/awesome_bar"/>
<ImageButton android:id="@+id/stop"
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dip"
android:layout_centerVertical="true"
android:layout_alignRight="@id/awesome_bar"
android:orientation="horizontal">
<ImageButton android:id="@+id/reader"
style="@style/AddressBar.ImageButton"
android:layout_width="24dip"
android:layout_height="24dip"
android:layout_marginRight="10dip"
android:layout_centerVertical="true"
android:src="@drawable/urlbar_stop"
android:layout_alignRight="@id/awesome_bar"
android:src="@drawable/reader"
android:visibility="gone"/>
<ImageButton android:id="@+id/site_security"
style="@style/AddressBar.ImageButton"
android:layout_width="24dip"
android:layout_height="24dip"
android:layout_marginRight="10dip"
android:layout_centerVertical="true"
android:src="@drawable/site_security_level"
android:layout_alignRight="@id/awesome_bar"/>
android:visibility="gone"/>
<ImageButton android:id="@+id/stop"
style="@style/AddressBar.ImageButton"
android:layout_width="24dip"
android:layout_height="24dip"
android:src="@drawable/urlbar_stop"
android:visibility="gone"/>
</LinearLayout>
<ImageView android:id="@+id/shadow"
android:layout_width="fill_parent"

Просмотреть файл

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
android:orientation="vertical"
android:background="@drawable/menu_popup_bg">
<Button android:id="@+id/reader_mode"
android:layout_width="fill_parent"
android:layout_height="44dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:background="@drawable/menu_item_bg"
android:gravity="center_vertical|left"
android:textColor="@color/menu_item_title"
android:textSize="16sp"
android:text="@string/reader_mode"/>
<Button android:id="@+id/reading_list"
android:layout_width="fill_parent"
android:layout_height="44dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:background="@drawable/menu_item_bg"
android:gravity="center_vertical|left"
android:textColor="@color/menu_item_title"
android:textSize="16sp"
android:text="@string/add_to_reading_list"/>
</LinearLayout>
<ImageView android:id="@+id/arrow"
android:layout_width="28dip"
android:layout_height="10dip"
android:layout_gravity="top|center_horizontal"
android:src="@drawable/menu_popup_arrow"
android:scaleType="fitXY"/>
</FrameLayout>

Просмотреть файл

@ -111,6 +111,11 @@
<string name="site_settings_clear">&site_settings_clear;</string>
<string name="site_settings_no_settings">&site_settings_no_settings;</string>
<string name="add_to_reading_list">&add_to_reading_list;</string>
<string name="reading_list_added">&reading_list_added;</string>
<string name="reading_list_removed">&reading_list_removed;</string>
<string name="reader_mode">&reader_mode;</string>
<string name="contextmenu_open_new_tab">&contextmenu_open_new_tab;</string>
<string name="contextmenu_remove_history">&contextmenu_remove_history;</string>
<string name="contextmenu_remove_bookmark">&contextmenu_remove_bookmark;</string>

Просмотреть файл

@ -32,6 +32,7 @@ public class testBrowserProvider extends ContentProviderTest {
private String TAGS_FOLDER_GUID;
private String TOOLBAR_FOLDER_GUID;
private String UNFILED_FOLDER_GUID;
private String READING_LIST_FOLDER_GUID;
private Uri mBookmarksUri;
private Uri mBookmarksPositionUri;
@ -77,12 +78,15 @@ public class testBrowserProvider extends ContentProviderTest {
private String mCombinedIdCol;
private String mCombinedBookmarkIdCol;
private String mCombinedHistoryIdCol;
private String mCombinedDisplayCol;
private String mCombinedUrlCol;
private String mCombinedTitleCol;
private String mCombinedVisitsCol;
private String mCombinedLastVisitedCol;
private String mCombinedFaviconCol;
private String mCombinedThumbnailCol;
private int mCombinedDisplayNormal;
private int mCombinedDisplayReader;
private void loadContractInfo() throws Exception {
mBookmarksUri = getContentUri("Bookmarks");
@ -98,6 +102,7 @@ public class testBrowserProvider extends ContentProviderTest {
TAGS_FOLDER_GUID = getStringColumn("Bookmarks", "TAGS_FOLDER_GUID");
TOOLBAR_FOLDER_GUID = getStringColumn("Bookmarks", "TOOLBAR_FOLDER_GUID");
UNFILED_FOLDER_GUID = getStringColumn("Bookmarks", "UNFILED_FOLDER_GUID");
READING_LIST_FOLDER_GUID = getStringColumn("Bookmarks", "READING_LIST_FOLDER_GUID");
mBookmarksIdCol = getStringColumn("Bookmarks", "_ID");
mBookmarksTitleCol = getStringColumn("Bookmarks", "TITLE");
@ -137,12 +142,16 @@ public class testBrowserProvider extends ContentProviderTest {
mCombinedIdCol = getStringColumn("Combined", "_ID");
mCombinedBookmarkIdCol = getStringColumn("Combined", "BOOKMARK_ID");
mCombinedHistoryIdCol = getStringColumn("Combined", "HISTORY_ID");
mCombinedDisplayCol = getStringColumn("Combined", "DISPLAY");
mCombinedUrlCol = getStringColumn("Combined", "URL");
mCombinedTitleCol = getStringColumn("Combined", "TITLE");
mCombinedVisitsCol = getStringColumn("Combined", "VISITS");
mCombinedLastVisitedCol = getStringColumn("Combined", "DATE_LAST_VISITED");
mCombinedFaviconCol = getStringColumn("Combined", "FAVICON");
mCombinedThumbnailCol = getStringColumn("Combined", "THUMBNAIL");
mCombinedDisplayNormal = getIntColumn("Combined", "DISPLAY_NORMAL");
mCombinedDisplayReader = getIntColumn("Combined", "DISPLAY_READER");
}
private void loadMobileFolderId() throws Exception {
@ -163,16 +172,18 @@ public class testBrowserProvider extends ContentProviderTest {
guid + " != ? AND " +
guid + " != ? AND " +
guid + " != ? AND " +
guid + " != ? AND " +
guid + " != ?",
new String[] { PLACES_FOLDER_GUID,
MOBILE_FOLDER_GUID,
MENU_FOLDER_GUID,
TAGS_FOLDER_GUID,
TOOLBAR_FOLDER_GUID,
UNFILED_FOLDER_GUID});
UNFILED_FOLDER_GUID,
READING_LIST_FOLDER_GUID });
c = mProvider.query(appendUriParam(mBookmarksUri, "PARAM_SHOW_DELETED", "1"), null, null, null, null);
mAsserter.is(c.getCount(), 6, "All non-special bookmarks and folders were deleted");
mAsserter.is(c.getCount(), 7, "All non-special bookmarks and folders were deleted");
mProvider.delete(appendUriParam(mHistoryUri, "PARAM_IS_SYNC", "1"), null, null);
c = mProvider.query(appendUriParam(mHistoryUri, "PARAM_SHOW_DELETED", "1"), null, null, null, null);
@ -304,6 +315,7 @@ public class testBrowserProvider extends ContentProviderTest {
mTests.add(new TestBatchOperations());
mTests.add(new TestCombinedView());
mTests.add(new TestCombinedViewDisplay());
}
public void testBrowserProvider() throws Exception {
@ -522,18 +534,21 @@ public class testBrowserProvider extends ContentProviderTest {
mBookmarksGuidCol + " = ? OR " +
mBookmarksGuidCol + " = ? OR " +
mBookmarksGuidCol + " = ? OR " +
mBookmarksGuidCol + " = ? OR " +
mBookmarksGuidCol + " = ?",
new String[] { PLACES_FOLDER_GUID,
MOBILE_FOLDER_GUID,
MENU_FOLDER_GUID,
TAGS_FOLDER_GUID,
TOOLBAR_FOLDER_GUID,
UNFILED_FOLDER_GUID },
UNFILED_FOLDER_GUID,
READING_LIST_FOLDER_GUID },
null);
mAsserter.is(c.getCount(), 6, "Right number of special folders");
mAsserter.is(c.getCount(), 7, "Right number of special folders");
int rootId = getIntColumn("Bookmarks", "FIXED_ROOT_ID");
int readingListId = getIntColumn("Bookmarks", "FIXED_READING_LIST_ID");
while (c.moveToNext()) {
int id = c.getInt(c.getColumnIndex(mBookmarksIdCol));
@ -542,6 +557,8 @@ public class testBrowserProvider extends ContentProviderTest {
if (guid.equals(PLACES_FOLDER_GUID)) {
mAsserter.is(new Integer(id), new Integer(rootId), "The id of places folder is correct");
} else if (guid.equals(READING_LIST_FOLDER_GUID)) {
mAsserter.is(new Integer(id), new Integer(readingListId), "The id of reading list folder is correct");
}
mAsserter.is(new Integer(parentId), new Integer(rootId),
@ -1416,4 +1433,59 @@ public class testBrowserProvider extends ContentProviderTest {
BOOKMARK_THUMBNAIL, "Combined entry has bookmark thumbnail image");
}
}
class TestCombinedViewDisplay extends Test {
public void test() throws Exception {
final String TITLE_1 = "Test Page 1";
final String TITLE_2 = "Test Page 2";
final String TITLE_3_HISTORY = "Test Page 3 (History Entry)";
final String TITLE_3_BOOKMARK = "Test Page 3 (Bookmark Entry)";
final String TITLE_4 = "Test Page 4";
final String URL_1 = "http://example.com";
final String URL_2 = "http://example.org";
final String URL_3 = "http://examples2.com";
final String URL_4 = "http://readinglist.com";
final int VISITS = 10;
final long LAST_VISITED = System.currentTimeMillis();
// Create a basic history entry
ContentValues basicHistory = createHistoryEntry(TITLE_1, URL_1, VISITS, LAST_VISITED);
ContentUris.parseId(mProvider.insert(mHistoryUri, basicHistory));
// Create a basic bookmark entry
ContentValues basicBookmark = createBookmark(TITLE_2, URL_2, mMobileFolderId,
mBookmarksTypeBookmark, 0, "tags", "description", "keyword");
mProvider.insert(mBookmarksUri, basicBookmark);
// Create a history entry and bookmark entry with the same URL to
// represent a visited bookmark
ContentValues combinedHistory = createHistoryEntry(TITLE_3_HISTORY, URL_3, VISITS, LAST_VISITED);
mProvider.insert(mHistoryUri, combinedHistory);
ContentValues combinedBookmark = createBookmark(TITLE_3_BOOKMARK, URL_3, mMobileFolderId,
mBookmarksTypeBookmark, 0, "tags", "description", "keyword");
mProvider.insert(mBookmarksUri, combinedBookmark);
// Create a reading list entry
int readingListId = getIntColumn("Bookmarks", "FIXED_READING_LIST_ID");
ContentValues readingListItem = createBookmark(TITLE_4, URL_4, readingListId,
mBookmarksTypeBookmark, 0, "tags", "description", "keyword");
long readingListItemId = ContentUris.parseId(mProvider.insert(mBookmarksUri, readingListItem));
Cursor c = mProvider.query(mCombinedUri, null, "", null, null);
mAsserter.is(c.getCount(), 4, "4 combined entries found");
while (c.moveToNext()) {
long id = c.getLong(c.getColumnIndex(mCombinedBookmarkIdCol));
int display = c.getInt(c.getColumnIndex(mCombinedDisplayCol));
int expectedDisplay = (id == readingListItemId ? mCombinedDisplayReader : mCombinedDisplayNormal);
mAsserter.is(new Integer(display), new Integer(expectedDisplay),
"Combined display column should always be DISPLAY_READER for the reading list item");
}
}
}
}

Просмотреть файл

@ -64,6 +64,7 @@
<string name="bookmarks_folder_unfiled">&bookmarks.folder.unfiled.label;</string>
<string name="bookmarks_folder_desktop">&bookmarks.folder.desktop.label;</string>
<string name="bookmarks_folder_mobile">&bookmarks.folder.mobile.label;</string>
<string name="bookmarks_folder_reading_list">&bookmarks.folder.readinglist.label;</string>
<!-- Notification strings -->
<string name="sync_notification_oneaccount">&sync.notification.oneaccount.label;</string>