Merge autoland to central, a=merge

This commit is contained in:
Wes Kocher 2016-10-17 15:11:40 -07:00
Родитель bf78a6ebaf 0ca67120dc
Коммит 6e39e47451
56 изменённых файлов: 1230 добавлений и 331 удалений

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

@ -475,8 +475,12 @@ toolbar:not(#TabsToolbar) > #personal-bookmarks {
display: none;
}
#PopupAutoComplete {
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#browser-autocomplete-result-popup");
#PopupAutoComplete > richlistbox > richlistitem > .ac-type-icon,
#PopupAutoComplete > richlistbox > richlistitem > .ac-site-icon,
#PopupAutoComplete > richlistbox > richlistitem > .ac-tags,
#PopupAutoComplete > richlistbox > richlistitem > .ac-separator,
#PopupAutoComplete > richlistbox > richlistitem > .ac-url {
display: none;
}
#PopupSearchAutoComplete {

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

@ -141,7 +141,7 @@
<tooltip id="remoteBrowserTooltip"/>
<!-- for search and content formfill/pw manager -->
<panel type="autocomplete" id="PopupAutoComplete" noautofocus="true" hidden="true"/>
<panel type="autocomplete-richlistbox" id="PopupAutoComplete" noautofocus="true" hidden="true"/>
<!-- for search with one-off buttons -->
<panel type="autocomplete" id="PopupSearchAutoComplete" noautofocus="true" hidden="true"/>

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

@ -914,6 +914,10 @@ notification[value="translation"] menulist > .menulist-dropmarker {
display: block;
}
/* AutoComplete */
%include ../shared/autocomplete.inc.css
#treecolAutoCompleteImage {
max-width : 36px;
}

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

@ -1750,6 +1750,8 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button {
/* ----- AUTOCOMPLETE ----- */
%include ../shared/autocomplete.inc.css
#treecolAutoCompleteImage {
max-width: 36px;
}

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

@ -0,0 +1,20 @@
%if 0
/* 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/. */
%endif
#PopupAutoComplete > richlistbox > richlistitem {
height: 20px;
min-height: 20px;
border-radius: 0;
}
#PopupAutoComplete > richlistbox > richlistitem > .ac-title {
font: icon;
margin-inline-start: 6px;
}
#PopupAutoComplete > richlistbox {
padding: 0;
}

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

@ -1532,6 +1532,8 @@ html|*.urlbar-input:-moz-lwtheme::placeholder,
/* autocomplete */
%include ../shared/autocomplete.inc.css
#treecolAutoCompleteImage {
max-width: 36px;
}

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

@ -33,6 +33,15 @@ const int kLeftStickYAxis = 1;
const int kRightStickXAxis = 2;
const int kRightStickYAxis = 3;
// Standard channel is used for managing gamepads that
// are from GamepadPlatformService. VR channel
// is for gamepads that are from VRManagerChild.
enum class GamepadServiceType : uint16_t {
Standard,
VR,
NumGamepadServiceType
};
class Gamepad final : public nsISupports,
public nsWrapperCache
{

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

@ -28,6 +28,7 @@
#include "nsIObserverService.h"
#include "nsIServiceManager.h"
#include "nsThreadUtils.h"
#include "VRManagerChild.h"
#include "mozilla/Services.h"
#include "mozilla/Unused.h"
@ -50,6 +51,7 @@ const nsTArray<RefPtr<nsGlobalWindow>>::index_type NoIndex =
bool sShutdown = false;
StaticRefPtr<GamepadManager> gGamepadManagerSingleton;
const uint32_t VR_GAMEPAD_IDX_OFFSET = 0x01 << 16;
} // namespace
@ -108,6 +110,11 @@ GamepadManager::StopMonitoring()
}
mChannelChildren.Clear();
mGamepads.Clear();
#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
mVRChannelChild = gfx::VRManagerChild::Get();
mVRChannelChild->SendControllerListenerRemoved();
#endif
}
void
@ -188,10 +195,36 @@ GamepadManager::GetGamepad(uint32_t aIndex) const
return nullptr;
}
uint32_t GamepadManager::GetGamepadIndexWithServiceType(uint32_t aIndex,
GamepadServiceType aServiceType)
{
uint32_t newIndex = 0;
switch (aServiceType) {
case GamepadServiceType::Standard:
{
MOZ_ASSERT(aIndex <= VR_GAMEPAD_IDX_OFFSET);
newIndex = aIndex;
break;
}
case GamepadServiceType::VR:
{
newIndex = aIndex + VR_GAMEPAD_IDX_OFFSET;
break;
}
default:
MOZ_ASSERT(false);
break;
}
return newIndex;
}
void
GamepadManager::AddGamepad(uint32_t aIndex,
const nsAString& aId,
GamepadMappingType aMapping,
GamepadServiceType aServiceType,
uint32_t aNumButtons,
uint32_t aNumAxes)
{
@ -204,24 +237,28 @@ GamepadManager::AddGamepad(uint32_t aIndex,
aNumButtons,
aNumAxes);
uint32_t newIndex = GetGamepadIndexWithServiceType(aIndex, aServiceType);
// We store the gamepad related to its index given by the parent process,
// and no duplicate index is allowed.
MOZ_ASSERT(!mGamepads.Get(aIndex, nullptr));
mGamepads.Put(aIndex, gamepad);
NewConnectionEvent(aIndex, true);
MOZ_ASSERT(!mGamepads.Get(newIndex, nullptr));
mGamepads.Put(newIndex, gamepad);
NewConnectionEvent(newIndex, true);
}
void
GamepadManager::RemoveGamepad(uint32_t aIndex)
GamepadManager::RemoveGamepad(uint32_t aIndex, GamepadServiceType aServiceType)
{
RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
uint32_t newIndex = GetGamepadIndexWithServiceType(aIndex, aServiceType);
RefPtr<Gamepad> gamepad = GetGamepad(newIndex);
if (!gamepad) {
NS_WARNING("Trying to delete gamepad with invalid index");
return;
}
gamepad->SetConnected(false);
NewConnectionEvent(aIndex, false);
mGamepads.Remove(aIndex);
NewConnectionEvent(newIndex, false);
mGamepads.Remove(newIndex);
}
void
@ -544,13 +581,13 @@ GamepadManager::Update(const GamepadChangeEvent& aEvent)
if (aEvent.type() == GamepadChangeEvent::TGamepadAdded) {
const GamepadAdded& a = aEvent.get_GamepadAdded();
AddGamepad(a.index(), a.id(),
static_cast<GamepadMappingType>(a.mapping()),
a.mapping(), a.service_type(),
a.num_buttons(), a.num_axes());
return;
}
if (aEvent.type() == GamepadChangeEvent::TGamepadRemoved) {
const GamepadRemoved& a = aEvent.get_GamepadRemoved();
RemoveGamepad(a.index());
RemoveGamepad(a.index(), a.service_type());
return;
}
if (aEvent.type() == GamepadChangeEvent::TGamepadButtonInformation) {
@ -584,8 +621,13 @@ GamepadManager::ActorCreated(PBackgroundChild *aActor)
child->SendGamepadListenerAdded();
mChannelChildren.AppendElement(child);
// TODO: Add more event channels to mChannelChildren if you would
// like to support more kinds of devices.
#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
// Construct VRManagerChannel and ask adding the connected
// VR controllers to GamepadManager
mVRChannelChild = gfx::VRManagerChild::Get();
mVRChannelChild->SetGamepadManager(this);
mVRChannelChild->SendControllerListenerAdded();
#endif
}
//Override nsIIPCBackgroundChildCreateCallback

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

@ -15,6 +15,9 @@
class nsGlobalWindow;
namespace mozilla {
namespace gfx {
class VRManagerChild;
} // namespace gfx
namespace dom {
class EventTarget;
@ -47,10 +50,10 @@ class GamepadManager final : public nsIObserver,
// Add a gamepad to the list of known gamepads.
void AddGamepad(uint32_t aIndex, const nsAString& aID, GamepadMappingType aMapping,
uint32_t aNumButtons, uint32_t aNumAxes);
GamepadServiceType aServiceType, uint32_t aNumButtons, uint32_t aNumAxes);
// Remove the gamepad at |aIndex| from the list of known gamepads.
void RemoveGamepad(uint32_t aIndex);
void RemoveGamepad(uint32_t aIndex, GamepadServiceType aServiceType);
// Update the state of |aButton| for the gamepad at |aIndex| for all
// windows that are listening and visible, and fire one of
@ -113,6 +116,7 @@ class GamepadManager final : public nsIObserver,
// will be destroyed during the IPDL shutdown chain, so we
// don't need to refcount it here.
nsTArray<GamepadEventChannelChild *> mChannelChildren;
gfx::VRManagerChild* mVRChannelChild;
private:
@ -127,6 +131,9 @@ class GamepadManager final : public nsIObserver,
// Indicate that a window has received data from a gamepad.
void SetWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex,
bool aHasSeen = true);
// Our gamepad index has VR_GAMEPAD_IDX_OFFSET while GamepadChannelType
// is from VRManager.
uint32_t GetGamepadIndexWithServiceType(uint32_t aIndex, GamepadServiceType aServiceType);
// Gamepads connected to the system. Copies of these are handed out
// to each window.

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

@ -87,7 +87,7 @@ GamepadPlatformService::AddGamepad(const char* aID,
uint32_t index = ++mGamepadIndex;
GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), index,
(uint32_t)aMapping, aNumButtons, aNumAxes);
aMapping, GamepadServiceType::Standard, aNumButtons, aNumAxes);
NotifyGamepadChange<GamepadAdded>(a);
return index;
}
@ -99,7 +99,7 @@ GamepadPlatformService::RemoveGamepad(uint32_t aIndex)
// platform-dependent backends
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
GamepadRemoved a(aIndex);
GamepadRemoved a(aIndex, GamepadServiceType::Standard);
NotifyGamepadChange<GamepadRemoved>(a);
}

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

@ -122,8 +122,12 @@ GamepadServiceTest::AddGamepad(const nsAString& aID,
return nullptr;
}
// Because GamepadServiceTest::AddGamepad() is opened for Web API,
// we need to convert aMapping from uint32_t to GamepadMappingType here.
GamepadAdded a(nsString(aID), 0,
(uint32_t)aMapping, aNumButtons, aNumAxes);
static_cast<GamepadMappingType>(aMapping),
GamepadServiceType::Standard,
aNumButtons, aNumAxes);
GamepadChangeEvent e(a);
nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
@ -150,7 +154,7 @@ GamepadServiceTest::RemoveGamepad(uint32_t aIndex)
return;
}
GamepadRemoved a(aIndex);
GamepadRemoved a(aIndex, GamepadServiceType::Standard);
GamepadChangeEvent e(a);
uint32_t id = ++mEventNumber;

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

@ -8,6 +8,7 @@
#define mozilla_dom_GamepadServiceTest_h_
#include "nsIIPCBackgroundChildCreateCallback.h"
#include "mozilla/dom/GamepadBinding.h"
namespace mozilla {
namespace dom {

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

@ -2,19 +2,25 @@
* 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/. */
using mozilla::dom::GamepadMappingType from "mozilla/dom/GamepadMessageUtils.h";
using mozilla::dom::GamepadServiceType from "mozilla/dom/GamepadMessageUtils.h";
namespace mozilla {
namespace dom {
struct GamepadAdded {
nsString id;
uint32_t index;
uint32_t mapping;
GamepadMappingType mapping;
GamepadServiceType service_type;
uint32_t num_buttons;
uint32_t num_axes;
};
struct GamepadRemoved {
uint32_t index;
GamepadServiceType service_type;
};
struct GamepadAxisInformation {

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

@ -0,0 +1,24 @@
#ifndef mozilla_dom_gamepad_GamepadMessageUtils_h
#define mozilla_dom_gamepad_GamepadMessageUtils_h
#include "ipc/IPCMessageUtils.h"
#include "mozilla/dom/Gamepad.h"
namespace IPC {
template<>
struct ParamTraits<mozilla::dom::GamepadMappingType> :
public ContiguousEnumSerializer<mozilla::dom::GamepadMappingType,
mozilla::dom::GamepadMappingType(mozilla::dom::GamepadMappingType::_empty),
mozilla::dom::GamepadMappingType(mozilla::dom::GamepadMappingType::EndGuard_)> {};
template<>
struct ParamTraits<mozilla::dom::GamepadServiceType> :
public ContiguousEnumSerializer<mozilla::dom::GamepadServiceType,
mozilla::dom::GamepadServiceType(0),
mozilla::dom::GamepadServiceType(
mozilla::dom::GamepadServiceType::NumGamepadServiceType)> {};
} // namespace IPC
#endif // mozilla_dom_gamepad_GamepadMessageUtils_h

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

@ -23,7 +23,7 @@ GamepadTestChannelParent::RecvGamepadTestEvent(const uint32_t& aID,
nsCString gamepadID;
LossyCopyUTF16toASCII(a.id(), gamepadID);
uint32_t index = service->AddGamepad(gamepadID.get(),
(GamepadMappingType)a.mapping(),
a.mapping(),
a.num_buttons(),
a.num_axes());
if (!mShuttingdown) {

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

@ -20,6 +20,7 @@ if CONFIG['MOZ_GAMEPAD']:
'GamepadServiceTest.h',
'ipc/GamepadEventChannelChild.h',
'ipc/GamepadEventChannelParent.h',
'ipc/GamepadMessageUtils.h',
'ipc/GamepadTestChannelChild.h',
'ipc/GamepadTestChannelParent.h'
]

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

@ -31,7 +31,6 @@ TextTrackList::TextTrackList(nsPIDOMWindowInner* aOwnerWindow)
TextTrackList::TextTrackList(nsPIDOMWindowInner* aOwnerWindow,
TextTrackManager* aTextTrackManager)
: DOMEventTargetHelper(aOwnerWindow)
, mPendingTextTrackChange(false)
, mTextTrackManager(aTextTrackManager)
{
}

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

@ -67,7 +67,7 @@ public:
IMPL_EVENT_HANDLER(addtrack)
IMPL_EVENT_HANDLER(removetrack)
bool mPendingTextTrackChange;
bool mPendingTextTrackChange = false;
private:
~TextTrackList();

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

@ -19,7 +19,7 @@ using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::layers;
VRDisplayHost::VRDisplayHost(VRDisplayType aType)
VRDisplayHost::VRDisplayHost(VRDeviceType aType)
: mInputFrameID(0)
{
MOZ_COUNT_CTOR(VRDisplayHost);
@ -144,3 +144,15 @@ VRDisplayHost::CheckClearDisplayInfoDirty()
mLastUpdateDisplayInfo = mDisplayInfo;
return true;
}
VRControllerHost::VRControllerHost(VRDeviceType aType)
{
MOZ_COUNT_CTOR(VRControllerHost);
mControllerInfo.mType = aType;
mControllerInfo.mControllerID = VRDisplayManager::AllocateDisplayID();
}
VRControllerHost::~VRControllerHost()
{
MOZ_COUNT_DTOR(VRControllerHost);
}

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

@ -52,7 +52,7 @@ public:
bool CheckClearDisplayInfoDirty();
protected:
explicit VRDisplayHost(VRDisplayType aType);
explicit VRDisplayHost(VRDeviceType aType);
virtual ~VRDisplayHost();
#if defined(XP_WIN)
@ -83,6 +83,19 @@ private:
VRDisplayInfo mLastUpdateDisplayInfo;
};
class VRControllerHost {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRControllerHost)
const VRControllerInfo& GetControllerInfo() const { return mControllerInfo; }
protected:
explicit VRControllerHost(VRDeviceType aType);
virtual ~VRControllerHost();
VRControllerInfo mControllerInfo;
};
} // namespace gfx
} // namespace mozilla

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

@ -10,6 +10,7 @@
#include "gfxVROpenVR.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/VRDisplay.h"
#include "mozilla/dom/GamepadEventTypes.h"
#include "mozilla/layers/TextureHost.h"
#include "mozilla/Unused.h"
@ -51,6 +52,7 @@ VRManager::VRManager()
MOZ_ASSERT(sVRManagerSingleton == nullptr);
RefPtr<VRDisplayManager> mgr;
RefPtr<VRControllerManager> controllerMgr;
/**
* We must add the VRDisplayManager's to mManagers in a careful order to
@ -81,6 +83,11 @@ VRManager::VRManager()
mManagers.AppendElement(mgr);
}
controllerMgr = VRControllerManagerOpenVR::Create();
if (mgr) {
mControllerManagers.AppendElement(controllerMgr);
}
// OSVR is cross platform compatible
mgr = VRDisplayManagerOSVR::Create();
if (mgr) {
@ -103,6 +110,11 @@ VRManager::Destroy()
for (uint32_t i = 0; i < mManagers.Length(); ++i) {
mManagers[i]->Destroy();
}
mVRControllers.Clear();
for (uint32_t i = 0; i < mControllerManagers.Length(); ++i) {
mControllerManagers[i]->Destroy();
}
mInitialized = false;
}
@ -112,6 +124,10 @@ VRManager::Init()
for (uint32_t i = 0; i < mManagers.Length(); ++i) {
mManagers[i]->Init();
}
for (uint32_t i = 0; i < mControllerManagers.Length(); ++i) {
mControllerManagers[i]->Init();
}
mInitialized = true;
}
@ -169,12 +185,16 @@ VRManager::NotifyVsync(const TimeStamp& aVsyncTimestamp)
if (mLastRefreshTime.IsNull()) {
// This is the first vsync, must refresh VR displays
RefreshVRDisplays();
RefreshVRControllers();
mLastRefreshTime = TimeStamp::Now();
} else {
// We don't have to do this every frame, so check if we
// have refreshed recently.
TimeDuration duration = TimeStamp::Now() - mLastRefreshTime;
if (duration.ToMilliseconds() > kVRDisplayRefreshMaxDuration) {
RefreshVRDisplays();
RefreshVRControllers();
mLastRefreshTime = TimeStamp::Now();
}
}
}
@ -236,8 +256,6 @@ VRManager::RefreshVRDisplays(bool aMustDispatch)
if (displayInfoChanged || aMustDispatch) {
DispatchVRDisplayInfoUpdate();
}
mLastRefreshTime = TimeStamp::Now();
}
void
@ -289,5 +307,82 @@ VRManager::SubmitFrame(VRLayerParent* aLayer, layers::PTextureParent* aTexture,
}
}
RefPtr<gfx::VRControllerHost>
VRManager::GetController(const uint32_t& aControllerID)
{
RefPtr<gfx::VRControllerHost> controller;
if (mVRControllers.Get(aControllerID, getter_AddRefs(controller))) {
return controller;
}
return nullptr;
}
void
VRManager::GetVRControllerInfo(nsTArray<VRControllerInfo>& aControllerInfo)
{
aControllerInfo.Clear();
for (auto iter = mVRControllers.Iter(); !iter.Done(); iter.Next()) {
gfx::VRControllerHost* controller = iter.UserData();
aControllerInfo.AppendElement(VRControllerInfo(controller->GetControllerInfo()));
}
}
void
VRManager::RefreshVRControllers()
{
nsTArray<RefPtr<gfx::VRControllerHost>> controllers;
for (uint32_t i = 0; i < mControllerManagers.Length()
&& controllers.Length() == 0; ++i) {
mControllerManagers[i]->GetControllers(controllers);
}
bool controllerInfoChanged = false;
if (controllers.Length() != mVRControllers.Count()) {
// Catch cases where VR controllers has been removed
controllerInfoChanged = true;
}
for (const auto& controller : controllers) {
if (!GetController(controller->GetControllerInfo().GetControllerID())) {
// This is a new controller
controllerInfoChanged = true;
break;
}
}
if (controllerInfoChanged) {
mVRControllers.Clear();
for (const auto& controller : controllers) {
mVRControllers.Put(controller->GetControllerInfo().GetControllerID(),
controller);
}
}
for (uint32_t i = 0; i < mControllerManagers.Length(); ++i) {
mControllerManagers[i]->HandleInput();
}
}
void
VRManager::ScanForDevices()
{
for (uint32_t i = 0; i < mControllerManagers.Length(); ++i) {
mControllerManagers[i]->ScanForDevices();
}
}
template<class T>
void
VRManager::NotifyGamepadChange(const T& aInfo)
{
dom::GamepadChangeEvent e(aInfo);
for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
Unused << iter.Get()->GetKey()->SendGamepadUpdate(e);
}
}
} // namespace gfx
} // namespace mozilla

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

@ -22,6 +22,7 @@ namespace gfx {
class VRLayerParent;
class VRManagerParent;
class VRDisplayHost;
class VRControllerManager;
class VRManager
{
@ -37,12 +38,16 @@ public:
void NotifyVsync(const TimeStamp& aVsyncTimestamp);
void NotifyVRVsync(const uint32_t& aDisplayID);
void RefreshVRDisplays(bool aMustDispatch = false);
void ScanForDevices();
template<class T> void NotifyGamepadChange(const T& aInfo);
RefPtr<gfx::VRDisplayHost> GetDisplay(const uint32_t& aDisplayID);
void GetVRDisplayInfo(nsTArray<VRDisplayInfo>& aDisplayInfo);
void SubmitFrame(VRLayerParent* aLayer, layers::PTextureParent* aTexture,
const gfx::Rect& aLeftEyeRect,
const gfx::Rect& aRightEyeRect);
RefPtr<gfx::VRControllerHost> GetController(const uint32_t& aControllerID);
void GetVRControllerInfo(nsTArray<VRControllerInfo>& aControllerInfo);
protected:
VRManager();
@ -55,6 +60,7 @@ private:
void Destroy();
void DispatchVRDisplayInfoUpdate();
void RefreshVRControllers();
typedef nsTHashtable<nsRefPtrHashKey<VRManagerParent>> VRManagerParentSet;
VRManagerParentSet mVRManagerParents;
@ -62,9 +68,15 @@ private:
typedef nsTArray<RefPtr<VRDisplayManager>> VRDisplayManagerArray;
VRDisplayManagerArray mManagers;
typedef nsTArray<RefPtr<VRControllerManager>> VRControllerManagerArray;
VRControllerManagerArray mControllerManagers;
typedef nsRefPtrHashtable<nsUint32HashKey, gfx::VRDisplayHost> VRDisplayHostHashMap;
VRDisplayHostHashMap mVRDisplays;
typedef nsRefPtrHashtable<nsUint32HashKey, gfx::VRControllerHost> VRControllerHostHashMap;
VRControllerHostHashMap mVRControllers;
Atomic<bool> mInitialized;
TimeStamp mLastRefreshTime;

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

@ -6,6 +6,7 @@
#include <math.h>
#include "gfxVR.h"
#include "mozilla/dom/Gamepad.h"
#ifndef M_PI
# define M_PI 3.14159265358979323846
@ -15,6 +16,7 @@ using namespace mozilla;
using namespace mozilla::gfx;
Atomic<uint32_t> VRDisplayManager::sDisplayBase(0);
Atomic<uint32_t> VRControllerManager::sControllerBase(0);
/* static */ uint32_t
VRDisplayManager::AllocateDisplayID()
@ -55,3 +57,34 @@ VRFieldOfView::ConstructProjectionMatrix(float zNear, float zFar,
return mobj;
}
/* static */ uint32_t
VRControllerManager::AllocateControllerID()
{
return ++sControllerBase;
}
void
VRControllerManager::AddGamepad(const char* aID,
dom::GamepadMappingType aMapping,
uint32_t aNumButtons, uint32_t aNumAxes)
{
dom::GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), mControllerCount,
aMapping, dom::GamepadServiceType::VR, aNumButtons,
aNumAxes);
VRManager* vm = VRManager::Get();
MOZ_ASSERT(vm);
vm->NotifyGamepadChange<dom::GamepadAdded>(a);
}
void
VRControllerManager::NewButtonEvent(uint32_t aIndex, uint32_t aButton,
bool aPressed, double aValue)
{
dom::GamepadButtonInformation a(aIndex, aButton, aPressed, aValue);
VRManager* vm = VRManager::Get();
MOZ_ASSERT(vm);
vm->NotifyGamepadChange<dom::GamepadButtonInformation>(a);
}

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

@ -15,6 +15,7 @@
#include "mozilla/EnumeratedArray.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/TypedEnumBits.h"
#include "mozilla/dom/GamepadBinding.h"
namespace mozilla {
namespace layers {
@ -23,12 +24,13 @@ class PTextureParent;
namespace gfx {
class VRLayerParent;
class VRDisplayHost;
class VRControllerHost;
enum class VRDisplayType : uint16_t {
enum class VRDeviceType : uint16_t {
Oculus,
OpenVR,
OSVR,
NumVRDisplayTypes
NumVRDeviceTypes
};
enum class VRDisplayCapabilityFlags : uint16_t {
@ -122,7 +124,7 @@ struct VRFieldOfView {
struct VRDisplayInfo
{
VRDisplayType GetType() const { return mType; }
VRDeviceType GetType() const { return mType; }
uint32_t GetDisplayID() const { return mDisplayID; }
const nsCString& GetDisplayName() const { return mDisplayName; }
VRDisplayCapabilityFlags GetCapabilities() const { return mCapabilityFlags; }
@ -142,7 +144,7 @@ struct VRDisplayInfo
};
uint32_t mDisplayID;
VRDisplayType mType;
VRDeviceType mType;
nsCString mDisplayName;
VRDisplayCapabilityFlags mCapabilityFlags;
VRFieldOfView mEyeFOV[VRDisplayInfo::NumEyes];
@ -209,6 +211,60 @@ protected:
virtual ~VRDisplayManager() { }
};
struct VRControllerInfo
{
VRDeviceType GetType() const { return mType; }
uint32_t GetControllerID() const { return mControllerID; }
const nsCString& GetControllerName() const { return mControllerName; }
dom::GamepadMappingType GetMappingType() const { return mMappingType; }
uint32_t GetNumButtons() const { return mNumButtons; }
uint32_t GetNumAxes() const { return mNumAxes; }
uint32_t mControllerID;
VRDeviceType mType;
nsCString mControllerName;
dom::GamepadMappingType mMappingType;
uint32_t mNumButtons;
uint32_t mNumAxes;
bool operator==(const VRControllerInfo& other) const {
return mType == other.mType &&
mControllerID == other.mControllerID &&
mControllerName == other.mControllerName &&
mMappingType == other.mMappingType &&
mNumButtons == other.mNumButtons &&
mNumAxes == other.mNumAxes;
}
bool operator!=(const VRControllerInfo& other) const {
return !(*this == other);
}
};
class VRControllerManager {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRControllerManager)
static uint32_t AllocateControllerID();
virtual bool Init() = 0;
virtual void Destroy() = 0;
virtual void HandleInput() = 0;
virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult) = 0;
virtual void ScanForDevices() = 0;
void NewButtonEvent(uint32_t aIndex, uint32_t aButton,
bool aPressed, double aValue);
void AddGamepad(const char* aID, dom::GamepadMappingType aMapping,
uint32_t aNumButtons, uint32_t aNumAxes);
protected:
VRControllerManager() : mInstalled(false), mControllerCount(0) {}
virtual ~VRControllerManager() {}
bool mInstalled;
uint32_t mControllerCount;
static Atomic<uint32_t> sControllerBase;
};
} // namespace gfx
} // namespace mozilla

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

@ -203,7 +203,7 @@ SetFromTanRadians(double left, double right, double bottom, double top)
VRDisplayOSVR::VRDisplayOSVR(OSVR_ClientContext* context,
OSVR_ClientInterface* iface,
OSVR_DisplayConfig* display)
: VRDisplayHost(VRDisplayType::OSVR)
: VRDisplayHost(VRDeviceType::OSVR)
, m_ctx(context)
, m_iface(iface)
, m_display(display)

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

@ -315,7 +315,7 @@ FromFovPort(const ovrFovPort& aFOV)
} // namespace
VRDisplayOculus::VRDisplayOculus(ovrSession aSession)
: VRDisplayHost(VRDisplayType::Oculus)
: VRDisplayHost(VRDeviceType::Oculus)
, mSession(aSession)
, mTextureSet(nullptr)
, mQuadVS(nullptr)

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

@ -24,6 +24,7 @@
#include "nsServiceManagerUtils.h"
#include "nsIScreenManager.h"
#include "openvr/openvr.h"
#include "mozilla/dom/Gamepad.h"
#ifndef M_PI
# define M_PI 3.14159265358979323846
@ -33,6 +34,7 @@ using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::gfx::impl;
using namespace mozilla::layers;
using namespace mozilla::dom;
namespace {
extern "C" {
@ -85,7 +87,7 @@ LoadOpenVRRuntime()
VRDisplayOpenVR::VRDisplayOpenVR(::vr::IVRSystem *aVRSystem,
::vr::IVRChaperone *aVRChaperone,
::vr::IVRCompositor *aVRCompositor)
: VRDisplayHost(VRDisplayType::OpenVR)
: VRDisplayHost(VRDeviceType::OpenVR)
, mVRSystem(aVRSystem)
, mVRChaperone(aVRChaperone)
, mVRCompositor(aVRCompositor)
@ -440,3 +442,142 @@ VRDisplayManagerOpenVR::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
aHMDResult.AppendElement(mOpenVRHMD);
}
}
VRControllerOpenVR::VRControllerOpenVR()
: VRControllerHost(VRDeviceType::OpenVR)
{
MOZ_COUNT_CTOR_INHERITED(VRControllerOpenVR, VRControllerHost);
mControllerInfo.mControllerName.AssignLiteral("OpenVR HMD");
}
VRControllerOpenVR::~VRControllerOpenVR()
{
MOZ_COUNT_DTOR_INHERITED(VRControllerOpenVR, VRControllerHost);
}
VRControllerManagerOpenVR::VRControllerManagerOpenVR()
: mOpenVRInstalled(false), mVRSystem(nullptr)
{
}
VRControllerManagerOpenVR::~VRControllerManagerOpenVR()
{
Destroy();
}
/*static*/ already_AddRefed<VRControllerManagerOpenVR>
VRControllerManagerOpenVR::Create()
{
if (!gfxPrefs::VREnabled() || !gfxPrefs::VROpenVREnabled()) {
return nullptr;
}
RefPtr<VRControllerManagerOpenVR> manager = new VRControllerManagerOpenVR();
return manager.forget();
}
bool
VRControllerManagerOpenVR::Init()
{
if (mOpenVRInstalled)
return true;
if (!vr_IsRuntimeInstalled())
return false;
// Loading the OpenVR Runtime
vr::EVRInitError err = vr::VRInitError_None;
vr_InitInternal(&err, vr::VRApplication_Scene);
if (err != vr::VRInitError_None) {
return false;
}
mVRSystem = (vr::IVRSystem *)vr_GetGenericInterface(vr::IVRSystem_Version, &err);
if ((err != vr::VRInitError_None) || !mVRSystem) {
vr_ShutdownInternal();
return false;
}
mOpenVRInstalled = true;
return true;
}
void
VRControllerManagerOpenVR::Destroy()
{
mOpenVRController.Clear();
mOpenVRInstalled = false;
}
void
VRControllerManagerOpenVR::HandleInput()
{
MOZ_ASSERT(mVRSystem);
// Process OpenVR controller state
for (vr::TrackedDeviceIndex_t trackedDevice = 0;
trackedDevice < vr::k_unMaxTrackedDeviceCount; trackedDevice++ ) {
vr::VRControllerState_t state;
if (mVRSystem->GetTrackedDeviceClass(trackedDevice)
!= vr::TrackedDeviceClass_Controller) {
continue;
}
if (mVRSystem->GetControllerState(trackedDevice, &state)) {
if (state.ulButtonPressed) {
// TODO: For Bug 1299929 after landing, convert the button mask to an ID button
// NewButtonEvent(1,
// 0,
// 0,
// true);
}
}
}
return;
}
void
VRControllerManagerOpenVR::GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
{
if (!mOpenVRInstalled) {
return;
}
aControllerResult.Clear();
for (uint32_t i = 0; i < mOpenVRController.Length(); ++i) {
aControllerResult.AppendElement(mOpenVRController[i]);
}
}
void
VRControllerManagerOpenVR::ScanForDevices()
{
mControllerCount = 0;
mOpenVRController.Clear();
if (!mVRSystem)
return;
// Basically, we would have HMDs in the tracked devices, but we are just interested in the controllers.
for ( vr::TrackedDeviceIndex_t trackedDevice = vr::k_unTrackedDeviceIndex_Hmd + 1;
trackedDevice < vr::k_unMaxTrackedDeviceCount; ++trackedDevice ) {
if (!mVRSystem->IsTrackedDeviceConnected(trackedDevice)) {
continue;
}
if (mVRSystem->GetTrackedDeviceClass(trackedDevice) != vr::TrackedDeviceClass_Controller) {
continue;
}
RefPtr<VRControllerOpenVR> openVRController = new VRControllerOpenVR();
mOpenVRController.AppendElement(openVRController);
// Not already present, add it.
AddGamepad("OpenVR Gamepad", GamepadMappingType::_empty,
kOpenVRControllerAxes, kOpenVRControllerButtons);
++mControllerCount;
}
}

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

@ -86,6 +86,43 @@ protected:
bool mOpenVRInstalled;
};
namespace impl {
class VRControllerOpenVR : public VRControllerHost
{
public:
explicit VRControllerOpenVR();
protected:
virtual ~VRControllerOpenVR();
};
} // namespace impl
class VRControllerManagerOpenVR : public VRControllerManager
{
public:
static already_AddRefed<VRControllerManagerOpenVR> Create();
virtual bool Init() override;
virtual void Destroy() override;
virtual void HandleInput() override;
virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
aControllerResult) override;
virtual void ScanForDevices() override;
private:
VRControllerManagerOpenVR();
~VRControllerManagerOpenVR();
bool mOpenVRInstalled;
nsTArray<RefPtr<impl::VRControllerOpenVR>> mOpenVRController;
vr::IVRSystem *mVRSystem;
const uint32_t kOpenVRControllerButtons = 8;
const uint32_t kOpenVRControllerAxes = 5;
};
} // namespace gfx
} // namespace mozilla

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

@ -10,6 +10,7 @@ include protocol PLayer;
include protocol PTexture;
include protocol PVRLayer;
include LayersMessages;
include GamepadEventTypes;
include "VRMessageUtils.h";
@ -17,6 +18,7 @@ using struct mozilla::gfx::VRFieldOfView from "gfxVR.h";
using struct mozilla::gfx::VRDisplayInfo from "gfxVR.h";
using struct mozilla::gfx::VRSensorUpdate from "gfxVR.h";
using struct mozilla::gfx::VRHMDSensorState from "gfxVR.h";
using struct mozilla::gfx::VRControllerInfo from "gfxVR.h";
using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h";
using mozilla::layers::TextureFlags from "mozilla/layers/CompositorTypes.h";
@ -56,6 +58,12 @@ parent:
sync GetImmediateSensorState(uint32_t aDisplayID) returns(VRHMDSensorState aState);
sync SetHaveEventListener(bool aHaveEventListener);
async ControllerListenerAdded();
async ControllerListenerRemoved();
// GetControllers synchronously returns the VR controllers that have already been
// enumerated by RefreshVRControllers() but does not enumerate new ones.
sync GetControllers() returns(VRControllerInfo[] aControllerInfo);
child:
async ParentAsyncMessages(AsyncParentMessageData[] aMessages);
@ -68,6 +76,7 @@ child:
async NotifyVSync();
async NotifyVRVSync(uint32_t aDisplayID);
async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
async __delete__();

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

@ -8,12 +8,14 @@
#include "VRManagerChild.h"
#include "VRManagerParent.h"
#include "VRDisplayClient.h"
#include "nsGlobalWindow.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/layers/CompositorThread.h" // for CompositorThread
#include "mozilla/dom/Navigator.h"
#include "mozilla/dom/VREventObserver.h"
#include "mozilla/dom/WindowBinding.h" // for FrameRequestCallback
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/GamepadManager.h"
#include "mozilla/layers/TextureClient.h"
#include "nsContentUtils.h"
@ -37,6 +39,7 @@ void ReleaseVRManagerParentSingleton() {
VRManagerChild::VRManagerChild()
: TextureForwarder()
, mDisplaysInitialized(false)
, mGamepadManager(nullptr)
, mInputFrameID(-1)
, mMessageLoop(MessageLoop::current())
, mFrameRequestCallbackCounter(0)
@ -480,6 +483,19 @@ VRManagerChild::RecvNotifyVRVSync(const uint32_t& aDisplayID)
return true;
}
bool
VRManagerChild::RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent)
{
// VRManagerChild could be at other processes, but GamepadManager
// only exists at the content process or the parent process
// in non-e10s mode.
if (mGamepadManager) {
mGamepadManager->Update(aGamepadEvent);
}
return true;
}
void
VRManagerChild::RunFrameRequestCallbacks()
{
@ -573,5 +589,13 @@ VRManagerChild::FatalError(const char* const aName, const char* const aMsg) cons
dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aName, aMsg, OtherPid());
}
void
VRManagerChild::SetGamepadManager(dom::GamepadManager* aGamepadManager)
{
MOZ_ASSERT(aGamepadManager);
mGamepadManager = aGamepadManager;
}
} // namespace gfx
} // namespace mozilla

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

@ -17,6 +17,7 @@
namespace mozilla {
namespace dom {
class GamepadManager;
class Navigator;
class VRDisplay;
class VREventObserver;
@ -77,6 +78,9 @@ public:
int32_t *aHandle);
void CancelFrameRequestCallback(int32_t aHandle);
void RunFrameRequestCallbacks();
// GamepadManager has to be set by the content side to make sure we are using
// the same singleton GamepadManager from the same process.
void SetGamepadManager(dom::GamepadManager* aGamepadManager);
void UpdateDisplayInfo(nsTArray<VRDisplayInfo>& aDisplayUpdates);
void FireDOMVRDisplayConnectEvent();
@ -114,7 +118,7 @@ protected:
virtual bool RecvNotifyVSync() override;
virtual bool RecvNotifyVRVSync(const uint32_t& aDisplayID) override;
virtual bool RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) override;
// ShmemAllocator
@ -151,6 +155,7 @@ private:
nsTArray<RefPtr<VRDisplayClient> > mDisplays;
bool mDisplaysInitialized;
nsTArray<dom::Navigator*> mNavigatorCallbacks;
dom::GamepadManager* mGamepadManager;
int32_t mInputFrameID;

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

@ -291,5 +291,29 @@ VRManagerParent::RecvSetHaveEventListener(const bool& aHaveEventListener)
return true;
}
bool
VRManagerParent::RecvControllerListenerAdded()
{
VRManager* vm = VRManager::Get();
// Ask the connected gamepads to be added to GamepadManager
vm->ScanForDevices();
return true;
}
bool
VRManagerParent::RecvControllerListenerRemoved()
{
return true;
}
bool
VRManagerParent::RecvGetControllers(nsTArray<VRControllerInfo> *aControllers)
{
VRManager* vm = VRManager::Get();
vm->GetVRControllerInfo(*aControllers);
return true;
}
} // namespace gfx
} // namespace mozilla

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

@ -84,6 +84,9 @@ protected:
virtual bool RecvGetSensorState(const uint32_t& aDisplayID, VRHMDSensorState* aState) override;
virtual bool RecvGetImmediateSensorState(const uint32_t& aDisplayID, VRHMDSensorState* aState) override;
virtual bool RecvSetHaveEventListener(const bool& aHaveEventListener) override;
virtual bool RecvControllerListenerAdded() override;
virtual bool RecvControllerListenerRemoved() override;
virtual bool RecvGetControllers(nsTArray<VRControllerInfo> *aControllers) override;
private:
void RegisterWithManager();

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

@ -16,10 +16,10 @@
namespace IPC {
template<>
struct ParamTraits<mozilla::gfx::VRDisplayType> :
public ContiguousEnumSerializer<mozilla::gfx::VRDisplayType,
mozilla::gfx::VRDisplayType(0),
mozilla::gfx::VRDisplayType(mozilla::gfx::VRDisplayType::NumVRDisplayTypes)> {};
struct ParamTraits<mozilla::gfx::VRDeviceType> :
public ContiguousEnumSerializer<mozilla::gfx::VRDeviceType,
mozilla::gfx::VRDeviceType(0),
mozilla::gfx::VRDeviceType(mozilla::gfx::VRDeviceType::NumVRDeviceTypes)> {};
template<>
struct ParamTraits<mozilla::gfx::VRDisplayCapabilityFlags> :
@ -159,6 +159,35 @@ struct ParamTraits<mozilla::gfx::VRFieldOfView>
}
};
template <>
struct ParamTraits<mozilla::gfx::VRControllerInfo>
{
typedef mozilla::gfx::VRControllerInfo paramType;
static void Write(Message* aMsg, const paramType& aParam)
{
WriteParam(aMsg, aParam.mType);
WriteParam(aMsg, aParam.mControllerID);
WriteParam(aMsg, aParam.mControllerName);
WriteParam(aMsg, aParam.mMappingType);
WriteParam(aMsg, aParam.mNumButtons);
WriteParam(aMsg, aParam.mNumAxes);
}
static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
{
if (!ReadParam(aMsg, aIter, &(aResult->mType)) ||
!ReadParam(aMsg, aIter, &(aResult->mControllerID)) ||
!ReadParam(aMsg, aIter, &(aResult->mControllerName)) ||
!ReadParam(aMsg, aIter, &(aResult->mMappingType)) ||
!ReadParam(aMsg, aIter, &(aResult->mNumButtons)) ||
!ReadParam(aMsg, aIter, &(aResult->mNumAxes))) {
return false;
}
return true;
}
};
} // namespace IPC
#endif // mozilla_gfx_vr_VRMessageUtils_h

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

@ -16,6 +16,7 @@ EXPORTS += [
]
LOCAL_INCLUDES += [
'/dom/base',
'/gfx/layers/d3d11',
'/gfx/thebes',
]

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

@ -66,8 +66,10 @@ public class ActivityStream {
*
* This method implements the proposal from this desktop AS issue:
* https://github.com/mozilla/activity-stream/issues/1311
*
* @param usePath Use the path of the URL to extract a label (if suitable)
*/
public static String extractLabel(String url) {
public static String extractLabel(String url, boolean usePath) {
if (TextUtils.isEmpty(url)) {
return "";
}
@ -75,6 +77,7 @@ public class ActivityStream {
final Uri uri = Uri.parse(url);
// Use last path segment if suitable
if (usePath) {
final String segment = uri.getLastPathSegment();
if (!TextUtils.isEmpty(segment)
&& !UNDESIRED_LABELS.contains(segment)
@ -92,6 +95,7 @@ public class ActivityStream {
return segment;
}
}
}
// If no usable path segment was found then use the host without public suffix and common subdomains
final String host = uri.getHost();

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

@ -5,23 +5,26 @@
package org.mozilla.gecko.home.activitystream;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.Loader;
import android.support.v4.graphics.ColorUtils;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.util.TypedValue;
import android.widget.FrameLayout;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.home.HomePager;
import org.mozilla.gecko.home.SimpleCursorLoader;
import org.mozilla.gecko.home.activitystream.topsites.TopSitesPagerAdapter;
import org.mozilla.gecko.util.ContextUtils;
import org.mozilla.gecko.widget.RecyclerViewClickSupport;
public class ActivityStream extends FrameLayout {
@ -30,9 +33,18 @@ public class ActivityStream extends FrameLayout {
private static final int LOADER_ID_HIGHLIGHTS = 0;
private static final int LOADER_ID_TOPSITES = 1;
private static final int MINIMUM_TILES = 4;
private static final int MAXIMUM_TILES = 6;
private int desiredTileWidth;
private int desiredTilesHeight;
private int tileMargin;
public ActivityStream(Context context, AttributeSet attrs) {
super(context, attrs);
setBackgroundColor(ContextCompat.getColor(context, R.color.about_page_header_grey));
inflate(context, R.layout.as_content, this);
adapter = new StreamRecyclerAdapter();
@ -45,6 +57,11 @@ public class ActivityStream extends FrameLayout {
RecyclerViewClickSupport.addTo(rv)
.setOnItemClickListener(adapter);
final Resources resources = getResources();
desiredTileWidth = resources.getDimensionPixelSize(R.dimen.activity_stream_desired_tile_width);
desiredTilesHeight = resources.getDimensionPixelSize(R.dimen.activity_stream_desired_tile_height);
tileMargin = resources.getDimensionPixelSize(R.dimen.activity_stream_base_margin);
}
void setOnUrlOpenListener(HomePager.OnUrlOpenListener listener) {
@ -63,6 +80,36 @@ public class ActivityStream extends FrameLayout {
adapter.swapTopSitesCursor(null);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
int tiles = (w - tileMargin) / (desiredTileWidth + tileMargin);
if (tiles < MINIMUM_TILES) {
tiles = MINIMUM_TILES;
setPadding(0, 0, 0, 0);
} else if (tiles > MAXIMUM_TILES) {
tiles = MAXIMUM_TILES;
// Use the remaining space as padding
int needed = tiles * (desiredTileWidth + tileMargin) + tileMargin;
int padding = (w - needed) / 2;
w = needed;
setPadding(padding, 0, padding, 0);
} else {
setPadding(0, 0, 0, 0);
}
final float ratio = (float) desiredTilesHeight / (float) desiredTileWidth;
final int tilesWidth = (w - (tiles * tileMargin) - tileMargin) / tiles;
final int tilesHeight = (int) (ratio * tilesWidth);
adapter.setTileSize(tiles, tilesWidth, tilesHeight);
}
private class CursorLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
@ -71,7 +118,7 @@ public class ActivityStream extends FrameLayout {
return BrowserDB.from(context).getHighlights(context, 10);
} else if (id == LOADER_ID_TOPSITES) {
return BrowserDB.from(context).getActivityStreamTopSites(
context, TopSitesPagerAdapter.TOTAL_ITEMS);
context, TopSitesPagerAdapter.PAGES * MAXIMUM_TILES);
} else {
throw new IllegalArgumentException("Can't handle loader id " + id);
}

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

@ -4,11 +4,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.home.activitystream;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Color;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.mozilla.gecko.R;
@ -19,21 +26,21 @@ import org.mozilla.gecko.home.activitystream.topsites.TopSitesPagerAdapter;
import org.mozilla.gecko.icons.IconCallback;
import org.mozilla.gecko.icons.IconResponse;
import org.mozilla.gecko.icons.Icons;
import org.mozilla.gecko.util.DrawableUtil;
import org.mozilla.gecko.widget.FaviconView;
import java.util.concurrent.Future;
import static org.mozilla.gecko.activitystream.ActivityStream.extractLabel;
public abstract class StreamItem extends RecyclerView.ViewHolder {
public StreamItem(View itemView) {
super(itemView);
}
public void bind(Cursor cursor) {
throw new IllegalStateException("Cannot bind " + this.getClass().getSimpleName());
}
public static class TopPanel extends StreamItem {
public static final int LAYOUT_ID = R.layout.activity_stream_main_toppanel;
private final ViewPager topSitesPager;
public TopPanel(View itemView, HomePager.OnUrlOpenListener onUrlOpenListener) {
@ -46,17 +53,18 @@ public abstract class StreamItem extends RecyclerView.ViewHolder {
indicator.setViewPager(topSitesPager);
}
@Override
public void bind(Cursor cursor) {
((TopSitesPagerAdapter) topSitesPager.getAdapter()).swapCursor(cursor);
}
}
public void bind(Cursor cursor, int tiles, int tilesWidth, int tilesHeight) {
final TopSitesPagerAdapter adapter = (TopSitesPagerAdapter) topSitesPager.getAdapter();
adapter.setTilesSize(tiles, tilesWidth, tilesHeight);
adapter.swapCursor(cursor);
public static class BottomPanel extends StreamItem {
public static final int LAYOUT_ID = R.layout.activity_stream_main_bottompanel;
final Resources resources = itemView.getResources();
final int tilesMargin = resources.getDimensionPixelSize(R.dimen.activity_stream_base_margin);
final int textHeight = resources.getDimensionPixelSize(R.dimen.activity_stream_top_sites_text_height);
public BottomPanel(View itemView) {
super(itemView);
ViewGroup.LayoutParams layoutParams = topSitesPager.getLayoutParams();
layoutParams.height = tilesHeight + tilesMargin + textHeight;
topSitesPager.setLayoutParams(layoutParams);
}
}
@ -67,19 +75,29 @@ public abstract class StreamItem extends RecyclerView.ViewHolder {
final TextView vLabel;
final TextView vTimeSince;
final TextView vSourceView;
final TextView vPageView;
private Future<IconResponse> ongoingIconLoad;
private int tilesMargin;
public HighlightItem(View itemView) {
super(itemView);
tilesMargin = itemView.getResources().getDimensionPixelSize(R.dimen.activity_stream_base_margin);
vLabel = (TextView) itemView.findViewById(R.id.card_history_label);
vTimeSince = (TextView) itemView.findViewById(R.id.card_history_time_since);
vIconView = (FaviconView) itemView.findViewById(R.id.icon);
vSourceView = (TextView) itemView.findViewById(R.id.card_history_source);
vPageView = (TextView) itemView.findViewById(R.id.page);
ImageView menuButton = (ImageView) itemView.findViewById(R.id.menu);
menuButton.setImageDrawable(
DrawableUtil.tintDrawable(menuButton.getContext(), R.drawable.menu, Color.LTGRAY));
}
@Override
public void bind(Cursor cursor) {
public void bind(Cursor cursor, int tilesWidth, int tilesHeight) {
final long time = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Highlights.DATE));
final String ago = DateUtils.getRelativeTimeSpanString(time, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0).toString();
final String url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
@ -87,7 +105,13 @@ public abstract class StreamItem extends RecyclerView.ViewHolder {
vLabel.setText(cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.TITLE)));
vTimeSince.setText(ago);
ViewGroup.LayoutParams layoutParams = vIconView.getLayoutParams();
layoutParams.width = tilesWidth - tilesMargin;
layoutParams.height = tilesHeight;
vIconView.setLayoutParams(layoutParams);
updateSource(cursor);
updatePage(url);
if (ongoingIconLoad != null) {
ongoingIconLoad.cancel(true);
@ -117,6 +141,16 @@ public abstract class StreamItem extends RecyclerView.ViewHolder {
vSourceView.setText(vSourceView.getText());
}
private void updatePage(String url) {
final String label = extractLabel(url, false);
if (!TextUtils.isEmpty(label)) {
vPageView.setText(label);
} else {
vPageView.setText(url);
}
}
@Override
public void onIconResponse(IconResponse response) {
vIconView.updateImage(response);

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

@ -2,6 +2,7 @@
* 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/. */
package org.mozilla.gecko.home.activitystream;
import android.database.Cursor;
@ -12,7 +13,6 @@ import android.view.ViewGroup;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.home.HomePager;
import org.mozilla.gecko.home.activitystream.StreamItem.BottomPanel;
import org.mozilla.gecko.home.activitystream.StreamItem.HighlightItem;
import org.mozilla.gecko.home.activitystream.StreamItem.TopPanel;
import org.mozilla.gecko.widget.RecyclerViewClickSupport;
@ -25,16 +25,26 @@ public class StreamRecyclerAdapter extends RecyclerView.Adapter<StreamItem> impl
private HomePager.OnUrlOpenListener onUrlOpenListener;
private int tiles;
private int tilesWidth;
private int tilesHeight;
void setOnUrlOpenListener(HomePager.OnUrlOpenListener onUrlOpenListener) {
this.onUrlOpenListener = onUrlOpenListener;
}
public void setTileSize(int tiles, int tilesWidth, int tilesHeight) {
this.tilesWidth = tilesWidth;
this.tilesHeight = tilesHeight;
this.tiles = tiles;
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return TopPanel.LAYOUT_ID;
} else if (position == getItemCount() - 1) {
return BottomPanel.LAYOUT_ID;
} else {
return HighlightItem.LAYOUT_ID;
}
@ -46,8 +56,6 @@ public class StreamRecyclerAdapter extends RecyclerView.Adapter<StreamItem> impl
if (type == TopPanel.LAYOUT_ID) {
return new TopPanel(inflater.inflate(type, parent, false), onUrlOpenListener);
} else if (type == BottomPanel.LAYOUT_ID) {
return new BottomPanel(inflater.inflate(type, parent, false));
} else if (type == HighlightItem.LAYOUT_ID) {
return new HighlightItem(inflater.inflate(type, parent, false));
} else {
@ -56,8 +64,7 @@ public class StreamRecyclerAdapter extends RecyclerView.Adapter<StreamItem> impl
}
private int translatePositionToCursor(int position) {
if (position == 0 ||
position == getItemCount() - 1) {
if (position == 0) {
throw new IllegalArgumentException("Requested cursor position for invalid item");
}
@ -73,9 +80,9 @@ public class StreamRecyclerAdapter extends RecyclerView.Adapter<StreamItem> impl
final int cursorPosition = translatePositionToCursor(position);
highlightsCursor.moveToPosition(cursorPosition);
holder.bind(highlightsCursor);
((HighlightItem) holder).bind(highlightsCursor, tilesWidth, tilesHeight);
} else if (type == TopPanel.LAYOUT_ID) {
holder.bind(topSitesCursor);
((TopPanel) holder).bind(topSitesCursor, tiles, tilesWidth, tilesHeight);
}
}
@ -104,7 +111,7 @@ public class StreamRecyclerAdapter extends RecyclerView.Adapter<StreamItem> impl
highlightsCount = 0;
}
return 2 + highlightsCount;
return highlightsCount + 1;
}
public void swapHighlightsCursor(Cursor cursor) {

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

@ -4,17 +4,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.home.activitystream.topsites;
import android.database.Cursor;
import android.support.v7.widget.CardView;
import android.graphics.Color;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.activitystream.ActivityStream;
import org.mozilla.gecko.icons.IconCallback;
import org.mozilla.gecko.icons.IconResponse;
import org.mozilla.gecko.icons.Icons;
import org.mozilla.gecko.util.DrawableUtil;
import org.mozilla.gecko.widget.FaviconView;
import java.util.concurrent.Future;
@ -23,31 +24,28 @@ class TopSitesCard extends RecyclerView.ViewHolder implements IconCallback {
private final FaviconView faviconView;
private final TextView title;
private final View menuButton;
private final ImageView menuButton;
private Future<IconResponse> ongoingIconLoad;
private String url;
public TopSitesCard(CardView card) {
public TopSitesCard(FrameLayout card) {
super(card);
faviconView = (FaviconView) card.findViewById(R.id.favicon);
title = (TextView) card.findViewById(R.id.title);
menuButton = card.findViewById(R.id.menu);
menuButton = (ImageView) card.findViewById(R.id.menu);
}
void bind(Cursor cursor) {
this.url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
title.setText(cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.TITLE)));
void bind(TopSitesPageAdapter.TopSite topSite) {
final String label = ActivityStream.extractLabel(topSite.url, true);
title.setText(label);
if (ongoingIconLoad != null) {
ongoingIconLoad.cancel(true);
}
ongoingIconLoad = Icons.with(itemView.getContext())
.pageUrl(url)
.pageUrl(topSite.url)
.skipNetwork()
.build()
.execute(this);
@ -56,5 +54,10 @@ class TopSitesCard extends RecyclerView.ViewHolder implements IconCallback {
@Override
public void onIconResponse(IconResponse response) {
faviconView.updateImage(response);
final int tintColor = !response.hasColor() || response.getColor() == Color.WHITE ? Color.LTGRAY : Color.WHITE;
menuButton.setImageDrawable(
DrawableUtil.tintDrawable(menuButton.getContext(), R.drawable.menu, tintColor));
}
}

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

@ -23,12 +23,16 @@ public class TopSitesPage
@Nullable AttributeSet attrs) {
super(context, attrs);
setLayoutManager(new GridLayoutManager(context, TopSitesPagerAdapter.GRID_WIDTH));
setLayoutManager(new GridLayoutManager(context, 1));
RecyclerViewClickSupport.addTo(this)
.setOnItemClickListener(this);
}
public void setTiles(int tiles) {
setLayoutManager(new GridLayoutManager(getContext(), tiles));
}
private HomePager.OnUrlOpenListener onUrlOpenListener = null;
public TopSitesPageAdapter getAdapter() {

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

@ -4,52 +4,51 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.home.activitystream.topsites;
import android.content.Context;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.support.annotation.UiThread;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract;
import java.util.ArrayList;
import java.util.List;
public class TopSitesPageAdapter extends RecyclerView.Adapter<TopSitesCard> {
static final class TopSite {
public final long id;
public final String url;
/**
* Cursor wrapper that handles the offsets and limits that we expect.
* This allows most of our code to completely ignore the fact that we're only touching part
* of the cursor.
*/
private static final class SubsetCursor extends CursorWrapper {
private final int start;
private final int count;
public SubsetCursor(Cursor cursor, int start, int maxCount) {
super(cursor);
this.start = start;
if (start + maxCount < cursor.getCount()) {
count = maxCount;
} else {
count = cursor.getCount() - start;
TopSite(long id, String url) {
this.id = id;
this.url = url;
}
}
@Override
public boolean moveToPosition(int position) {
return super.moveToPosition(position + start);
}
private List<TopSite> topSites;
private int tiles;
private int tilesWidth;
private int tilesHeight;
private int textHeight;
@Override
public int getCount() {
return count;
}
}
public TopSitesPageAdapter(Context context, int tiles, int tilesWidth, int tilesHeight) {
setHasStableIds(true);
private Cursor cursor;
this.topSites = new ArrayList<>();
this.tiles = tiles;
this.tilesWidth = tilesWidth;
this.tilesHeight = tilesHeight;
this.textHeight = context.getResources().getDimensionPixelSize(R.dimen.activity_stream_top_sites_text_height);
}
/**
*
@ -58,14 +57,21 @@ public class TopSitesPageAdapter extends RecyclerView.Adapter<TopSitesCard> {
* 3 items will be displayed by this adapter.
*/
public void swapCursor(Cursor cursor, int startIndex) {
if (cursor != null) {
if (startIndex >= cursor.getCount()) {
throw new IllegalArgumentException("startIndex must be within Cursor range");
topSites.clear();
if (cursor == null) {
return;
}
this.cursor = new SubsetCursor(cursor, startIndex, TopSitesPagerAdapter.ITEMS_PER_PAGE);
} else {
this.cursor = null;
for (int i = 0; i < tiles; i++) {
cursor.moveToPosition(startIndex + i);
// The Combined View only contains pages that have been visited at least once, i.e. any
// page in the TopSites query will contain a HISTORY_ID. _ID however will be 0 for all rows.
final long id = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID));
final String url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
topSites.add(new TopSite(id, url));
}
notifyDataSetChanged();
@ -73,46 +79,37 @@ public class TopSitesPageAdapter extends RecyclerView.Adapter<TopSitesCard> {
@Override
public void onBindViewHolder(TopSitesCard holder, int position) {
cursor.moveToPosition(position);
holder.bind(cursor);
}
public TopSitesPageAdapter() {
setHasStableIds(true);
holder.bind(topSites.get(position));
}
@Override
public TopSitesCard onCreateViewHolder(ViewGroup parent, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final CardView card = (CardView) inflater.inflate(R.layout.activity_stream_topsites_card, parent, false);
final FrameLayout card = (FrameLayout) inflater.inflate(R.layout.activity_stream_topsites_card, parent, false);
final View content = card.findViewById(R.id.content);
ViewGroup.LayoutParams layoutParams = content.getLayoutParams();
layoutParams.width = tilesWidth;
layoutParams.height = tilesHeight + textHeight;
content.setLayoutParams(layoutParams);
return new TopSitesCard(card);
}
@UiThread
public String getURLForPosition(int position) {
cursor.moveToPosition(position);
return cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
return topSites.get(position).url;
}
@Override
public int getItemCount() {
if (cursor != null) {
return cursor.getCount();
} else {
return 0;
}
return topSites.size();
}
@Override
@UiThread
public long getItemId(int position) {
cursor.moveToPosition(position);
// The Combined View only contains pages that have been visited at least once, i.e. any
// page in the TopSites query will contain a HISTORY_ID. _ID however will be 0 for all rows.
return cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID));
return topSites.get(position).id;
}
}

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

@ -21,14 +21,11 @@ import java.util.LinkedList;
* all lower-level Adapters that populate the individual topsite items.
*/
public class TopSitesPagerAdapter extends PagerAdapter {
// Note: because of RecyclerView limitations we need to also adjust the layout height when
// GRID_HEIGHT is changed.
public static final int GRID_HEIGHT = 1;
public static final int GRID_WIDTH = 4;
public static final int PAGES = 4;
public static final int ITEMS_PER_PAGE = GRID_HEIGHT * GRID_WIDTH;
public static final int TOTAL_ITEMS = ITEMS_PER_PAGE * PAGES;
private int tiles;
private int tilesWidth;
private int tilesHeight;
private LinkedList<TopSitesPage> pages = new LinkedList<>();
@ -42,9 +39,15 @@ public class TopSitesPagerAdapter extends PagerAdapter {
this.onUrlOpenListener = onUrlOpenListener;
}
public void setTilesSize(int tiles, int tilesWidth, int tilesHeight) {
this.tilesWidth = tilesWidth;
this.tilesHeight = tilesHeight;
this.tiles = tiles;
}
@Override
public int getCount() {
return count;
return Math.min(count, 4);
}
@Override
@ -61,30 +64,35 @@ public class TopSitesPagerAdapter extends PagerAdapter {
return page;
}
@Override
public int getItemPosition(Object object) {
return PagerAdapter.POSITION_NONE;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
public void swapCursor(Cursor cursor) {
final int oldPages = getCount();
// Divide while rounding up: 0 items = 0 pages, 1-ITEMS_PER_PAGE items = 1 page, etc.
if (cursor != null) {
count = (cursor.getCount() - 1) / ITEMS_PER_PAGE + 1;
count = (cursor.getCount() - 1) / tiles + 1;
} else {
count = 0;
}
final int pageDelta = count - oldPages;
pages.clear();
final int pageDelta = count;
if (pageDelta > 0) {
final LayoutInflater inflater = LayoutInflater.from(context);
for (int i = 0; i < pageDelta; i++) {
final TopSitesPage page = (TopSitesPage) inflater.inflate(R.layout.activity_stream_topsites_page, null, false);
page.setTiles(tiles);
page.setOnUrlOpenListener(onUrlOpenListener);
page.setAdapter(new TopSitesPageAdapter());
page.setAdapter(new TopSitesPageAdapter(context, tiles, tilesWidth, tilesHeight));
pages.add(page);
}
} else if (pageDelta < 0) {
@ -103,7 +111,7 @@ public class TopSitesPagerAdapter extends PagerAdapter {
int startIndex = 0;
for (TopSitesPage page : pages) {
page.getAdapter().swapCursor(cursor, startIndex);
startIndex += ITEMS_PER_PAGE;
startIndex += tiles;
}
notifyDataSetChanged();

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

@ -3,14 +3,15 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginEnd="12dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="@dimen/activity_stream_base_margin"
android:layout_marginLeft="@dimen/activity_stream_base_margin"
android:layout_marginRight="@dimen/activity_stream_base_margin"
android:layout_marginStart="@dimen/activity_stream_base_margin"
android:layout_marginTop="0dp"
android:orientation="vertical">
@ -18,45 +19,87 @@
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="8dp">
android:padding="10dp">
<org.mozilla.gecko.widget.FaviconView
android:id="@+id/icon"
android:layout_width="@dimen/favicon_bg"
android:layout_height="@dimen/favicon_bg"
android:layout_gravity="center"
gecko:enableRoundCorners="false"
tools:background="@drawable/favicon_globe" />
<ImageView
android:id="@+id/menu"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_gravity="right|top"
android:padding="2dp"
android:layout_marginLeft="@dimen/activity_stream_base_margin"
android:layout_marginBottom="@dimen/activity_stream_base_margin"
android:src="@drawable/menu" />
<TextView
android:id="@+id/page"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="twitter"
android:textSize="12sp"
android:textColor="@color/activity_stream_subtitle"
android:layout_toRightOf="@id/icon"
android:layout_toEndOf="@id/icon"
android:layout_toLeftOf="@id/menu"
android:layout_toStartOf="@id/menu"
android:paddingLeft="@dimen/activity_stream_base_margin"
android:paddingStart="@dimen/activity_stream_base_margin"/>
<TextView
android:id="@+id/card_history_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/icon"
android:layout_toRightOf="@id/icon"
android:maxLines="2"
android:paddingLeft="8dp"
android:paddingStart="8dp"
android:maxLines="3"
android:ellipsize="end"
android:paddingLeft="@dimen/activity_stream_base_margin"
android:paddingStart="@dimen/activity_stream_base_margin"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="#ff000000"
tools:text="Descriptive title of a page..." />
android:layout_below="@id/page"
android:layout_toLeftOf="@id/menu"
android:layout_toStartOf="@id/menu"
tools:text="Descriptive title of a page that is veeeeeeery long - maybe even too long?" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_toRightOf="@id/icon"
android:paddingLeft="8dp"
android:paddingStart="8dp"
android:layout_toEndOf="@id/icon"
android:layout_alignParentBottom="true"
android:paddingLeft="@dimen/activity_stream_base_margin"
android:paddingStart="@dimen/activity_stream_base_margin"
android:paddingTop="4dp"
android:gravity="center_vertical"
android:layout_below="@id/card_history_label">
<ImageView
android:layout_width="12dp"
android:layout_height="12dp"
android:background="#ff8e8e8e" />
<TextView
android:id="@+id/card_history_source"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:textSize="12sp"
android:layout_weight="1"
android:textColor="#ff8e8e8e"
android:textColor="@color/activity_stream_subtitle"
tools:text="Bookmarked" />
<TextView
@ -64,7 +107,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="#ffd2d2d2"
android:textColor="@color/activity_stream_timestamp"
tools:text="20m" />
</LinearLayout>

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

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>

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

@ -7,8 +7,9 @@
android:orientation="vertical">
<android.support.v4.view.ViewPager
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="123dp"
android:layout_height="match_parent"
android:id="@+id/topsites_pager"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"

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

@ -1,15 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<org.mozilla.gecko.widget.FilledCardView
xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="115dp"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
android:layout_margin="1dp">
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<org.mozilla.gecko.widget.FaviconView
android:id="@+id/favicon"
@ -19,35 +18,42 @@
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_gravity="center"
tools:background="@drawable/favicon_globe"
gecko:enableRoundCorners="false"/>
gecko:enableRoundCorners="false"
tools:background="@drawable/favicon_globe" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_below="@id/favicon"
android:background="@color/activity_stream_divider" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentStart="true"
android:ellipsize="end"
android:gravity="center"
android:lines="1"
android:padding="4dp"
android:textSize="12sp"
android:textColor="@android:color/black"
tools:text="Lorem Ipsum here is a title"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"/>
tools:text="Lorem Ipsum here is a title" />
<ImageView
android:id="@+id/menu_button"
android:id="@+id/menu"
android:layout_width="wrap_content"
android:layout_height="32dp"
android:layout_height="28dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_gravity="right|top"
android:padding="6dp"
android:src="@drawable/menu"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"/>
android:src="@drawable/menu" />
</RelativeLayout>
</org.mozilla.gecko.widget.FilledCardView>
</android.support.v7.widget.CardView>

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

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<org.mozilla.gecko.home.activitystream.topsites.TopSitesPage xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="4dp"/>
android:paddingLeft="10dp"/>

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

@ -3,7 +3,6 @@
<android.support.v7.widget.RecyclerView
android:id="@+id/activity_stream_main_recyclerview"
android:background="@color/about_page_header_grey"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</merge>

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

@ -140,4 +140,8 @@
<color name="action_bar_bg_color">@color/toolbar_grey</color>
<color name="activity_stream_divider">#FFD2D2D2</color>
<color name="activity_stream_subtitle">#FF919191</color>
<color name="activity_stream_timestamp">#FFD3D3D3</color>
</resources>

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

@ -216,4 +216,9 @@
<item name="firstrun_tab_strip_content_start" type="dimen">15dp</item>
<item name="notification_media_cover" type="dimen">128dp</item>
<item name="activity_stream_base_margin" type="dimen">10dp</item>
<item name="activity_stream_desired_tile_width" type="dimen">90dp</item>
<item name="activity_stream_desired_tile_height" type="dimen">70dp</item>
<item name="activity_stream_top_sites_text_height" type="dimen">30dp</item>
</resources>

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

@ -18,78 +18,96 @@ public class TestActivityStream {
* https://gist.github.com/nchapman/36502ad115e8825d522a66549971a3f0
*/
@Test
public void testExtractLabel() {
public void testExtractLabelWithPath() {
// Empty values
assertEquals("", ActivityStream.extractLabel(""));
assertEquals("", ActivityStream.extractLabel(null));
assertEquals("", ActivityStream.extractLabel("", true));
assertEquals("", ActivityStream.extractLabel(null, true));
// Without path
assertEquals("news.ycombinator",
ActivityStream.extractLabel("https://news.ycombinator.com/"));
ActivityStream.extractLabel("https://news.ycombinator.com/", true));
assertEquals("sql.telemetry.mozilla",
ActivityStream.extractLabel("https://sql.telemetry.mozilla.org/"));
ActivityStream.extractLabel("https://sql.telemetry.mozilla.org/", true));
assertEquals("sso.mozilla",
ActivityStream.extractLabel("http://sso.mozilla.com/"));
ActivityStream.extractLabel("http://sso.mozilla.com/", true));
assertEquals("youtube",
ActivityStream.extractLabel("http://youtube.com/"));
ActivityStream.extractLabel("http://youtube.com/", true));
assertEquals("images.google",
ActivityStream.extractLabel("http://images.google.com/"));
ActivityStream.extractLabel("http://images.google.com/", true));
assertEquals("smile.amazon",
ActivityStream.extractLabel("http://smile.amazon.com/"));
ActivityStream.extractLabel("http://smile.amazon.com/", true));
assertEquals("localhost",
ActivityStream.extractLabel("http://localhost:5000/"));
ActivityStream.extractLabel("http://localhost:5000/", true));
assertEquals("independent",
ActivityStream.extractLabel("http://www.independent.co.uk/"));
ActivityStream.extractLabel("http://www.independent.co.uk/", true));
// With path
assertEquals("firefox",
ActivityStream.extractLabel("https://addons.mozilla.org/en-US/firefox/"));
ActivityStream.extractLabel("https://addons.mozilla.org/en-US/firefox/", true));
assertEquals("activity-stream",
ActivityStream.extractLabel("https://trello.com/b/KX3hV8XS/activity-stream"));
ActivityStream.extractLabel("https://trello.com/b/KX3hV8XS/activity-stream", true));
assertEquals("activity-stream",
ActivityStream.extractLabel("https://github.com/mozilla/activity-stream"));
ActivityStream.extractLabel("https://github.com/mozilla/activity-stream", true));
assertEquals("sidekiq",
ActivityStream.extractLabel("https://dispatch-news.herokuapp.com/sidekiq"));
ActivityStream.extractLabel("https://dispatch-news.herokuapp.com/sidekiq", true));
assertEquals("nchapman",
ActivityStream.extractLabel("https://github.com/nchapman/"));
ActivityStream.extractLabel("https://github.com/nchapman/", true));
// Unusable paths
assertEquals("phonebook.mozilla", // instead of "login"
ActivityStream.extractLabel("https://phonebook.mozilla.org/mellon/login?ReturnTo=https%3A%2F%2Fphonebook.mozilla.org%2F&IdP=http%3A%2F%2Fwww.okta.com"));
ActivityStream.extractLabel("https://phonebook.mozilla.org/mellon/login?ReturnTo=https%3A%2F%2Fphonebook.mozilla.org%2F&IdP=http%3A%2F%2Fwww.okta.com", true));
assertEquals("ipay.adp", // instead of "index.jsf"
ActivityStream.extractLabel("https://ipay.adp.com/iPay/index.jsf"));
ActivityStream.extractLabel("https://ipay.adp.com/iPay/index.jsf", true));
assertEquals("calendar.google", // instead of "render"
ActivityStream.extractLabel("https://calendar.google.com/calendar/render?pli=1#main_7"));
ActivityStream.extractLabel("https://calendar.google.com/calendar/render?pli=1#main_7", true));
assertEquals("myworkday", // instead of "home.htmld"
ActivityStream.extractLabel("https://www.myworkday.com/vhr_mozilla/d/home.htmld"));
ActivityStream.extractLabel("https://www.myworkday.com/vhr_mozilla/d/home.htmld", true));
assertEquals("mail.google", // instead of "1"
ActivityStream.extractLabel("https://mail.google.com/mail/u/1/#inbox"));
ActivityStream.extractLabel("https://mail.google.com/mail/u/1/#inbox", true));
assertEquals("docs.google", // instead of "edit"
ActivityStream.extractLabel("https://docs.google.com/presentation/d/11cyrcwhKTmBdEBIZ3szLO0-_Imrx2CGV2B9_LZHDrds/edit#slide=id.g15d41bb0f3_0_82"));
ActivityStream.extractLabel("https://docs.google.com/presentation/d/11cyrcwhKTmBdEBIZ3szLO0-_Imrx2CGV2B9_LZHDrds/edit#slide=id.g15d41bb0f3_0_82", true));
// Special cases
assertEquals("irccloud.mozilla",
ActivityStream.extractLabel("https://irccloud.mozilla.com/#!/ircs://irc1.dmz.scl3.mozilla.com:6697/%23universal-search"));
ActivityStream.extractLabel("https://irccloud.mozilla.com/#!/ircs://irc1.dmz.scl3.mozilla.com:6697/%23universal-search", true));
}
@Test
public void testExtractLabelWithoutPath() {
assertEquals("addons.mozilla",
ActivityStream.extractLabel("https://addons.mozilla.org/en-US/firefox/", false));
assertEquals("trello",
ActivityStream.extractLabel("https://trello.com/b/KX3hV8XS/activity-stream", false));
assertEquals("github",
ActivityStream.extractLabel("https://github.com/mozilla/activity-stream", false));
assertEquals("dispatch-news",
ActivityStream.extractLabel("https://dispatch-news.herokuapp.com/sidekiq", false));
assertEquals("github",
ActivityStream.extractLabel("https://github.com/nchapman/", false));
}
}

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

@ -13,49 +13,45 @@ this.EXPORTED_SYMBOLS = [ "AutoCompletePopup" ];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
// nsITreeView implementation that feeds the autocomplete popup
// with the search data.
var AutoCompleteTreeView = {
// AutoCompleteResultView is an abstraction around a list of results
// we got back up from browser-content.js. It implements enough of
// nsIAutoCompleteController and nsIAutoCompleteInput to make the
// richlistbox popup work.
var AutoCompleteResultView = {
// nsISupports
QueryInterface: XPCOMUtils.generateQI([Ci.nsITreeView,
Ci.nsIAutoCompleteController]),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteController,
Ci.nsIAutoCompleteInput]),
// Private variables
treeBox: null,
results: [],
// nsITreeView
selection: null,
get rowCount() { return this.results.length; },
setTree: function(treeBox) { this.treeBox = treeBox; },
getCellText: function(idx, column) { return this.results[idx].value },
isContainer: function(idx) { return false; },
getCellValue: function(idx, column) { return false },
isContainerOpen: function(idx) { return false; },
isContainerEmpty: function(idx) { return false; },
isSeparator: function(idx) { return false; },
isSorted: function() { return false; },
isEditable: function(idx, column) { return false; },
canDrop: function(idx, orientation, dt) { return false; },
getLevel: function(idx) { return 0; },
getParentIndex: function(idx) { return -1; },
hasNextSibling: function(idx, after) { return idx < this.results.length - 1 },
toggleOpenState: function(idx) { },
getCellProperties: function(idx, column) { return this.results[idx].style || ""; },
getRowProperties: function(idx) { return ""; },
getImageSrc: function(idx, column) { return null; },
getProgressMode : function(idx, column) { },
cycleHeader: function(column) { },
cycleCell: function(idx, column) { },
selectionChanged: function() { },
performAction: function(action) { },
performActionOnCell: function(action, index, column) { },
getColumnProperties: function(column) { return ""; },
// nsIAutoCompleteController
get matchCount() {
return this.rowCount;
return this.results.length;
},
getValueAt(index) {
return this.results[index].value;
},
getLabelAt(index) {
// Unused by richlist autocomplete - see getCommentAt.
return "";
},
getCommentAt(index) {
// The richlist autocomplete popup uses comment for its main
// display of an item, which is why we're returning the label
// here instead.
return this.results[index].label;
},
getStyleAt(index) {
return this.results[index].style;
},
getImageAt(index) {
return this.results[index].image;
},
handleEnter: function(aIsPopupSelection) {
@ -64,6 +60,21 @@ var AutoCompleteTreeView = {
stopSearch: function() {},
searchString: "",
// nsIAutoCompleteInput
get controller() {
return this;
},
get popup() {
return null;
},
_focus() {
AutoCompletePopup.requestFocus();
},
// Internal JS-only API
clearResults: function() {
this.results = [];
@ -99,10 +110,25 @@ this.AutoCompletePopup = {
},
handleEvent: function(evt) {
if (evt.type === "popuphidden") {
switch (evt.type) {
case "popupshowing": {
this.sendMessageToBrowser("FormAutoComplete:PopupOpened");
break;
}
case "popuphidden": {
AutoCompleteResultView.clearResults();
this.sendMessageToBrowser("FormAutoComplete:PopupClosed");
// adjustHeight clears the height from the popup so that
// we don't have a big shrink effect if we closed with a
// large list, and then open on a small one.
this.openedPopup.adjustHeight();
this.openedPopup = null;
this.weakBrowser = null;
evt.target.removeEventListener("popuphidden", this);
evt.target.removeEventListener("popupshowing", this);
break;
}
}
},
@ -134,20 +160,21 @@ this.AutoCompletePopup = {
this.openedPopup.setAttribute("width", Math.max(100, rect.width));
this.openedPopup.style.direction = dir;
AutoCompleteTreeView.setResults(results);
this.openedPopup.view = AutoCompleteTreeView;
AutoCompleteResultView.setResults(results);
this.openedPopup.view = AutoCompleteResultView;
this.openedPopup.selectedIndex = -1;
this.openedPopup.invalidate();
if (results.length) {
// Reset fields that were set from the last time the search popup was open
this.openedPopup.mInput = null;
this.openedPopup.mInput = AutoCompleteResultView;
this.openedPopup.showCommentColumn = false;
this.openedPopup.showImageColumn = false;
this.openedPopup.addEventListener("popuphidden", this);
this.openedPopup.addEventListener("popupshowing", this);
this.openedPopup.openPopupAtScreenRect("after_start", rect.left, rect.top,
rect.width, rect.height, false,
false);
this.openedPopup.addEventListener("popuphidden", this);
this.openedPopup.invalidate();
} else {
this.closePopup();
}
@ -161,19 +188,18 @@ this.AutoCompletePopup = {
if (!results.length) {
this.closePopup();
} else {
AutoCompleteTreeView.setResults(results);
// We need to re-set the view in order for the
// tree to know the view has changed.
this.openedPopup.view = AutoCompleteTreeView;
AutoCompleteResultView.setResults(results);
this.openedPopup.invalidate();
}
},
closePopup() {
if (this.openedPopup) {
this.openedPopup.closePopup();
// Note that hidePopup() closes the popup immediately,
// so popuphiding or popuphidden events will be fired
// and handled during this call.
this.openedPopup.hidePopup();
}
AutoCompleteTreeView.clearResults();
},
removeLogin(login) {
@ -189,7 +215,9 @@ this.AutoCompletePopup = {
switch (message.name) {
case "FormAutoComplete:SelectBy": {
if (this.openedPopup) {
this.openedPopup.selectBy(message.data.reverse, message.data.page);
}
break;
}
@ -233,7 +261,7 @@ this.AutoCompletePopup = {
// any cached data. This is necessary cause otherwise we'd clear data
// only when starting a new search, but the next input could not support
// autocomplete and it would end up inheriting the existing data.
AutoCompleteTreeView.clearResults();
AutoCompleteResultView.clearResults();
break;
}
}
@ -246,17 +274,41 @@ this.AutoCompletePopup = {
* Despite its name, handleEnter is what is called when the
* user clicks on one of the items in the popup.
*/
handleEnter: function(aIsPopupSelection) {
let browser = this.weakBrowser ? this.weakBrowser.get()
: null;
if (browser && this.openedPopup) {
browser.messageManager.sendAsyncMessage(
"FormAutoComplete:HandleEnter",
{ selectedIndex: this.openedPopup.selectedIndex,
isPopupSelection: aIsPopupSelection }
);
handleEnter(aIsPopupSelection) {
if (this.openedPopup) {
this.sendMessageToBrowser("FormAutoComplete:HandleEnter", {
selectedIndex: this.openedPopup.selectedIndex,
isPopupSelection: aIsPopupSelection,
});
}
},
stopSearch: function() {}
/**
* If a browser exists that AutoCompletePopup knows about,
* sends it a message. Otherwise, this is a no-op.
*
* @param {string} msgName
* The name of the message to send.
* @param {object} data
* The optional data to send with the message.
*/
sendMessageToBrowser(msgName, data) {
let browser = this.weakBrowser ? this.weakBrowser.get()
: null;
if (browser) {
browser.messageManager.sendAsyncMessage(msgName, data);
}
},
stopSearch: function() {},
/**
* Sends a message to the browser requesting that the input
* that the AutoCompletePopup is open for be focused.
*/
requestFocus: function() {
if (this.openedPopup) {
this.sendMessageToBrowser("FormAutoComplete:Focus");
}
},
}

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

@ -12,10 +12,9 @@ assert.ok(gAutocompletePopup, "Got autocomplete popup");
var ParentUtils = {
getMenuEntries() {
let entries = [];
let column = gAutocompletePopup.tree.columns[0];
let numRows = gAutocompletePopup.tree.view.rowCount;
let numRows = gAutocompletePopup.view.matchCount;
for (let i = 0; i < numRows; i++) {
entries.push(gAutocompletePopup.tree.view.getCellText(i, column));
entries.push(gAutocompletePopup.view.getValueAt(i));
}
return entries;
},
@ -69,11 +68,10 @@ var ParentUtils = {
checkRowCount(expectedCount, expectedFirstValue = null) {
ContentTaskUtils.waitForCondition(() => {
return gAutocompletePopup.tree.view.rowCount === expectedCount &&
return gAutocompletePopup.view.matchCount === expectedCount &&
(!expectedFirstValue ||
expectedCount <= 1 ||
gAutocompletePopup.tree.view.getCellText(0, gAutocompletePopup.tree.columns[0]) ===
expectedFirstValue);
gAutocompletePopup.view.getValueAt(0) === expectedFirstValue);
}, "Waiting for row count change: " + expectedCount + " First value: " + expectedFirstValue).then(() => {
let results = this.getMenuEntries();
sendAsyncMessage("gotMenuChange", { results });

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

@ -1398,46 +1398,96 @@ let AutoCompletePopup = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompletePopup]),
_connected: false,
MESSAGES: [
"FormAutoComplete:HandleEnter",
"FormAutoComplete:PopupClosed",
"FormAutoComplete:PopupOpened",
"FormAutoComplete:RequestFocus",
],
init: function() {
// We need to wait for a content viewer to be available
// before we can attach our AutoCompletePopup handler,
// since nsFormFillController assumes one will exist
// when we call attachToBrowser.
let onDCL = () => {
removeEventListener("DOMContentLoaded", onDCL);
// Hook up the form fill autocomplete controller.
let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
.getService(Ci.nsIFormFillController);
controller.attachToBrowser(docShell,
this.QueryInterface(Ci.nsIAutoCompletePopup));
this._connected = true;
};
addEventListener("DOMContentLoaded", onDCL);
addEventListener("unload", this);
addEventListener("DOMContentLoaded", this);
for (let messageName of this.MESSAGES) {
addMessageListener(messageName, this);
}
this._input = null;
this._popupOpen = false;
addMessageListener("FormAutoComplete:HandleEnter", message => {
this.selectedIndex = message.data.selectedIndex;
let controller = Components.classes["@mozilla.org/autocomplete/controller;1"].
getService(Components.interfaces.nsIAutoCompleteController);
controller.handleEnter(message.data.isPopupSelection);
});
addEventListener("unload", function() {
AutoCompletePopup.destroy();
});
},
destroy: function() {
if (this._connected) {
let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
.getService(Ci.nsIFormFillController);
controller.detachFromBrowser(docShell);
this._connected = false;
}
removeEventListener("unload", this);
removeEventListener("DOMContentLoaded", this);
for (let messageName of this.MESSAGES) {
removeMessageListener(messageName, this);
}
},
handleEvent(event) {
switch (event.type) {
case "DOMContentLoaded": {
removeEventListener("DOMContentLoaded", this);
// We need to wait for a content viewer to be available
// before we can attach our AutoCompletePopup handler,
// since nsFormFillController assumes one will exist
// when we call attachToBrowser.
// Hook up the form fill autocomplete controller.
let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
.getService(Ci.nsIFormFillController);
controller.attachToBrowser(docShell,
this.QueryInterface(Ci.nsIAutoCompletePopup));
this._connected = true;
break;
}
case "unload": {
this.destroy();
break;
}
}
},
receiveMessage(message) {
switch (message.name) {
case "FormAutoComplete:HandleEnter": {
this.selectedIndex = message.data.selectedIndex;
let controller = Cc["@mozilla.org/autocomplete/controller;1"]
.getService(Ci.nsIAutoCompleteController);
controller.handleEnter(message.data.isPopupSelection);
break;
}
case "FormAutoComplete:PopupClosed": {
this._popupOpen = false;
break;
}
case "FormAutoComplete:PopupOpened": {
this._popupOpen = true;
break;
}
case "FormAutoComplete:RequestFocus": {
if (this._input) {
this._input.focus();
}
break;
}
}
},
get input () { return this._input; },
@ -1471,10 +1521,13 @@ let AutoCompletePopup = {
sendAsyncMessage("FormAutoComplete:MaybeOpenPopup",
{ results, rect, dir });
this._input = input;
this._popupOpen = true;
},
closePopup: function () {
// We set this here instead of just waiting for the
// PopupClosed message to do it so that we don't end
// up in a state where the content thinks that a popup
// is open when it isn't (or soon won't be).
this._popupOpen = false;
sendAsyncMessage("FormAutoComplete:ClosePopup", {});
},
@ -1516,7 +1569,7 @@ let AutoCompletePopup = {
}
return results;
}
},
}
AutoCompletePopup.init();

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

@ -1124,6 +1124,10 @@ extends="chrome://global/content/bindings/popup.xml#popup">
<body>
<![CDATA[
if (!this.mPopupOpen) {
// It's possible that the panel is hidden initially
// to avoid impacting startup / new window performance
aInput.popup.hidden = false;
this.mInput = aInput;
// clear any previous selection, see bugs 400671 and 488357
this.selectedIndex = -1;