зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central into mozilla-inbound
This commit is contained in:
Коммит
4fff8472d8
|
@ -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.'>
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.6 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 2.1 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.9 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 2.4 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.3 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 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>
|
||||
|
|
Загрузка…
Ссылка в новой задаче