Merge from mozilla-central

This commit is contained in:
Ehsan Akhgari 2012-04-23 18:17:44 -04:00
commit 7ec790e011
50 changed files with 1352 additions and 322 deletions

View File

@ -157,7 +157,7 @@ __defineSetter__("PluralForm", function (val) {
});
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
"resource:///modules/TelemetryStopwatch.jsm");
"resource://gre/modules/TelemetryStopwatch.jsm");
#ifdef MOZ_SERVICES_SYNC
XPCOMUtils.defineLazyGetter(this, "Weave", function() {

View File

@ -105,5 +105,7 @@ function getIconForApp(aShell, callback) {
function onIconDownloaded(aShell, aMimeType, aStatusCode, aIcon, aCallback) {
if (Components.isSuccessCode(aStatusCode)) {
aShell.processIcon(aMimeType, aIcon, aCallback);
} else {
aCallback.call(aShell);
}
}

View File

@ -8,9 +8,9 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource:///modules/Services.jsm");
Cu.import("resource:///modules/FileUtils.jsm");
Cu.import("resource:///modules/NetUtil.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
let WebappsInstaller = {
/**
@ -495,7 +495,7 @@ MacNativeApp.prototype = {
throw(ex);
}
getIconForApp(this);
getIconForApp(this, this._createPListFile);
},
_removeInstallation: function(keepProfile) {
@ -561,7 +561,10 @@ MacNativeApp.prototype = {
writer.setString("Webapp", "Name", this.appName);
writer.setString("Webapp", "Profile", this.appProfileDir.leafName);
writer.writeFile();
},
_createPListFile: function() {
// ${InstallDir}/Contents/Info.plist
let infoPListContent = '<?xml version="1.0" encoding="UTF-8"?>\n\
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n\
<plist version="1.0">\n\
@ -616,7 +619,7 @@ MacNativeApp.prototype = {
* @param aCallback a callback function to be called
* after the process finishes
*/
processIcon: function(aMimeType, aIcon) {
processIcon: function(aMimeType, aIcon, aCallback) {
try {
let process = Cc["@mozilla.org/process/util;1"]
.createInstance(Ci.nsIProcess);
@ -633,6 +636,8 @@ MacNativeApp.prototype = {
9);
} catch(e) {
throw(e);
} finally {
aCallback.call(this);
}
}

View File

@ -11,7 +11,7 @@ let Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Webapps.jsm");
Cu.import("resource://gre/modules/WebappsInstaller.jsm");
Cu.import("resource:///modules/WebappsInstaller.jsm");
let webappsUI = {
init: function webappsUI_init() {

View File

@ -950,6 +950,10 @@ toolbar[iconsize="small"] #feed-button {
-moz-box-align: stretch;
}
.urlbar-input-box {
-moz-margin-start: 0;
}
.urlbar-history-dropmarker {
-moz-appearance: toolbarbutton-dropdown;
}
@ -1023,6 +1027,7 @@ toolbar[iconsize="small"] #feed-button {
#urlbar-display {
margin-top: 0;
margin-bottom: 0;
-moz-margin-start: 0;
color: GrayText;
}
@ -1033,7 +1038,7 @@ toolbar[iconsize="small"] #feed-button {
margin-top: 2px;
margin-bottom: 2px;
-moz-margin-start: 4px;
-moz-margin-end: 0;
-moz-margin-end: 3px;
list-style-image: url(chrome://browser/skin/identity-icons-generic.png);
-moz-image-region: rect(0, 16px, 16px, 0);
}
@ -1050,7 +1055,7 @@ toolbar[iconsize="small"] #feed-button {
-moz-image-region: rect(0, 32px, 16px, 16px);
}
#identity-box:hover:active > #identity-box-inner > #page-proxy-stack > #page-proxy-favicon,
#identity-box:hover:active > #identity-box-inner > #page-proxy-stack > #page-proxy-favicon,
#identity-box[open=true] > #identity-box-inner > #page-proxy-stack > #page-proxy-favicon {
-moz-image-region: rect(0, 48px, 16px, 32px);
}
@ -1082,7 +1087,6 @@ toolbar[iconsize="small"] #feed-button {
}
#identity-icon-labels {
-moz-margin-start: 4px;
-moz-padding-start: 2px;
-moz-padding-end: 5px;
}
@ -1091,6 +1095,7 @@ toolbar[iconsize="small"] #feed-button {
background-color: #fff;
color: hsl(92,81%,16%);
-moz-border-end: 1px solid hsla(92,81%,16%,.2);
-moz-margin-end: 4px;
}
/* Identity popup icons */

View File

@ -1025,7 +1025,6 @@ toolbar[mode="icons"] #zoom-in-button {
}
#identity-box.verifiedIdentity {
background-color: #fff;
color: hsl(92,100%,20%);
-moz-border-end: 1px solid hsla(92,81%,16%,.2);
-moz-padding-end: 4px;
@ -1098,7 +1097,7 @@ toolbar[mode="icons"] #zoom-in-button {
list-style-image: url(chrome://browser/skin/identity-icons-https-ev.png);
}
#identity-box:hover:active > #identity-box-inner > #page-proxy-stack > #page-proxy-favicon,
#identity-box:hover:active > #identity-box-inner > #page-proxy-stack > #page-proxy-favicon,
#identity-box[open=true] > #identity-box-inner > #page-proxy-stack > #page-proxy-favicon {
-moz-image-region: rect(0, 32px, 16px, 16px);
}

View File

@ -1299,6 +1299,10 @@ html|*.urlbar-input:-moz-lwtheme:-moz-placeholder,
.urlbar-textbox-container {
-moz-box-align: stretch;
}
.urlbar-input-box {
-moz-margin-start: 0;
}
#urlbar-icons {
-moz-box-align: center;
@ -1347,6 +1351,7 @@ html|*.urlbar-input:-moz-lwtheme:-moz-placeholder,
#urlbar-display {
margin-top: 0;
margin-bottom: 0;
-moz-margin-start: 0;
color: GrayText;
}
@ -1401,9 +1406,13 @@ html|*.urlbar-input:-moz-lwtheme:-moz-placeholder,
}
#identity-box.verifiedIdentity {
background-color: #fff;
color: hsl(92,100%,20%);
-moz-border-end: 1px solid hsla(92,81%,16%,.2);
-moz-margin-end: 4px;
}
#identity-box.verifiedIdentity:not(:-moz-lwtheme) {
background-color: #fff;
}
#identity-box:-moz-focusring {
@ -1412,8 +1421,8 @@ html|*.urlbar-input:-moz-lwtheme:-moz-placeholder,
}
#identity-icon-labels {
-moz-margin-start: 5px;
-moz-margin-end: 3px;
-moz-padding-start: 2px;
-moz-padding-end: 5px;
}
/* Location bar dropmarker */
@ -1448,10 +1457,7 @@ html|*.urlbar-input:-moz-lwtheme:-moz-placeholder,
#page-proxy-favicon {
width: 16px;
height: 16px;
margin-top: 1px;
margin-bottom: 1px;
-moz-margin-start: 4px;
-moz-margin-end: 0;
margin: 1px 3px;
list-style-image: url(chrome://browser/skin/identity-icons-generic.png);
-moz-image-region: rect(0, 16px, 16px, 0);
}

View File

@ -1,9 +1,9 @@
load 257752-1-recursion.xul
load 329335-1.xul
load 329884-1.xul
HTTP load 330010-1.xul
load 330012-1.xul
load 397148-1.xul
skip-if(winWidget) HTTP load 330010-1.xul # bug 742455
skip-if(winWidget) load 330012-1.xul # bug 742455
skip-if(winWidget) load 397148-1.xul # bug 742455
load 404346-1.xul
load 415019-1.xul
load 417840-1.xul

View File

@ -426,6 +426,24 @@ nsScreen::FullScreenEventListener::HandleEvent(nsIDOMEvent* aEvent)
nsCOMPtr<nsIDOMEventTarget> target;
aEvent->GetCurrentTarget(getter_AddRefs(target));
// We have to make sure that the event we got is the event sent when
// fullscreen is disabled because we could get one when fullscreen
// got enabled if the lock call is done at the same moment.
nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(target);
MOZ_ASSERT(window);
nsCOMPtr<nsIDOMDocument> doc;
window->GetDocument(getter_AddRefs(doc));
// If we have no doc, we will just continue, remove the event and unlock.
// This is an edge case were orientation lock and fullscreen is meaningless.
if (doc) {
bool fullscreen;
doc->GetMozFullScreen(&fullscreen);
if (fullscreen) {
return NS_OK;
}
}
target->RemoveSystemEventListener(NS_LITERAL_STRING("mozfullscreenchange"),
this, true);

View File

@ -35,14 +35,66 @@
*
* ***** END LICENSE BLOCK ***** */
#include <android/log.h>
#include "mozilla/Hal.h"
#include "AudioManager.h"
#include "gonk/AudioSystem.h"
using namespace mozilla::dom::gonk;
using namespace android;
using namespace mozilla::hal;
using namespace mozilla;
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "AudioManager" , ## args)
NS_IMPL_ISUPPORTS1(AudioManager, nsIAudioManager)
static AudioSystem::audio_devices
GetRoutingMode(int aType) {
if (aType == nsIAudioManager::FORCE_SPEAKER) {
return AudioSystem::DEVICE_OUT_SPEAKER;
} else if (aType == nsIAudioManager::FORCE_HEADPHONES) {
return AudioSystem::DEVICE_OUT_WIRED_HEADSET;
} else if (aType == nsIAudioManager::FORCE_BT_SCO) {
return AudioSystem::DEVICE_OUT_BLUETOOTH_SCO;
} else if (aType == nsIAudioManager::FORCE_BT_A2DP) {
return AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP;
} else {
return AudioSystem::DEVICE_IN_DEFAULT;
}
}
static void
InternalSetAudioRoutes(SwitchState aState)
{
if (aState == SWITCH_STATE_ON) {
AudioManager::SetAudioRoute(nsIAudioManager::FORCE_HEADPHONES);
} else if (aState == SWITCH_STATE_OFF) {
AudioManager::SetAudioRoute(nsIAudioManager::FORCE_SPEAKER);
}
}
class HeadphoneSwitchObserver : public SwitchObserver
{
public:
void Notify(const SwitchEvent& aEvent) {
InternalSetAudioRoutes(aEvent.status());
}
};
AudioManager::AudioManager() : mPhoneState(PHONE_STATE_CURRENT),
mObserver(new HeadphoneSwitchObserver())
{
RegisterSwitchObserver(SWITCH_HEADPHONES, mObserver);
InternalSetAudioRoutes(GetCurrentSwitchState(SWITCH_HEADPHONES));
}
AudioManager::~AudioManager() {
UnregisterSwitchObserver(SWITCH_HEADPHONES, mObserver);
}
NS_IMETHODIMP
AudioManager::GetMicrophoneMuted(bool* aMicrophoneMuted)
{
@ -161,3 +213,12 @@ AudioManager::GetForceForUse(PRInt32 aUsage, PRInt32* aForce) {
}
return NS_OK;
}
void
AudioManager::SetAudioRoute(int aRoutes) {
audio_io_handle_t handle = AudioSystem::getOutput(AudioSystem::SYSTEM);
String8 cmd;
cmd.appendFormat("%s=%d", AudioParameter::keyRouting, GetRoutingMode(aRoutes));
AudioSystem::setParameters(handle, cmd);
}

View File

@ -38,15 +38,22 @@
#ifndef mozilla_dom_system_b2g_audiomanager_h__
#define mozilla_dom_system_b2g_audiomanager_h__
#include "mozilla/Observer.h"
#include "nsAutoPtr.h"
#include "nsIAudioManager.h"
// {b2b51423-502d-4d77-89b3-7786b562b084}
#define NS_AUDIOMANAGER_CID {0x94f6fd70, 0x7615, 0x4af9, \
{0x89, 0x10, 0xf9, 0x3c, 0x55, 0xe6, 0x62, 0xec}}
#define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1"
namespace mozilla {
namespace hal {
class SwitchEvent;
typedef Observer<SwitchEvent> SwitchObserver;
} // namespace hal
namespace dom {
namespace gonk {
@ -56,16 +63,18 @@ public:
NS_DECL_ISUPPORTS
NS_DECL_NSIAUDIOMANAGER
AudioManager() : mPhoneState(PHONE_STATE_CURRENT)
{
}
AudioManager();
~AudioManager();
static void SetAudioRoute(int aRoutes);
protected:
PRInt32 mPhoneState;
private:
nsAutoPtr<mozilla::hal::SwitchObserver> mObserver;
};
} /* namespace telephony */
} /* namespace gonk */
} /* namespace dom */
} /* namespace mozilla */

View File

@ -67,3 +67,4 @@ XPCSHELL_TESTS = tests
endif
include $(topsrcdir)/config/rules.mk
include $(topsrcdir)/ipc/chromium/chromium-config.mk

View File

@ -596,5 +596,83 @@ UnlockScreenOrientation()
PROXY_IF_SANDBOXED(UnlockScreenOrientation());
}
void
EnableSwitchNotifications(hal::SwitchDevice aDevice) {
AssertMainThread();
PROXY_IF_SANDBOXED(EnableSwitchNotifications(aDevice));
}
void
DisableSwitchNotifications(hal::SwitchDevice aDevice) {
AssertMainThread();
PROXY_IF_SANDBOXED(DisableSwitchNotifications(aDevice));
}
hal::SwitchState GetCurrentSwitchState(hal::SwitchDevice aDevice)
{
AssertMainThread();
RETURN_PROXY_IF_SANDBOXED(GetCurrentSwitchState(aDevice));
}
typedef mozilla::ObserverList<SwitchEvent> SwitchObserverList;
static SwitchObserverList *sSwitchObserverLists = NULL;
static SwitchObserverList&
GetSwitchObserverList(hal::SwitchDevice aDevice) {
MOZ_ASSERT(0 <= aDevice && aDevice < NUM_SWITCH_DEVICE);
if (sSwitchObserverLists == NULL) {
sSwitchObserverLists = new SwitchObserverList[NUM_SWITCH_DEVICE];
}
return sSwitchObserverLists[aDevice];
}
static void
ReleaseObserversIfNeeded() {
for (int i = 0; i < NUM_SWITCH_DEVICE; i++) {
if (sSwitchObserverLists[i].Length() != 0)
return;
}
//The length of every list is 0, no observer in the list.
delete [] sSwitchObserverLists;
sSwitchObserverLists = NULL;
}
void
RegisterSwitchObserver(hal::SwitchDevice aDevice, hal::SwitchObserver *aObserver)
{
AssertMainThread();
SwitchObserverList& observer = GetSwitchObserverList(aDevice);
observer.AddObserver(aObserver);
if (observer.Length() == 1) {
EnableSwitchNotifications(aDevice);
}
}
void
UnregisterSwitchObserver(hal::SwitchDevice aDevice, hal::SwitchObserver *aObserver)
{
AssertMainThread();
SwitchObserverList& observer = GetSwitchObserverList(aDevice);
observer.RemoveObserver(aObserver);
if (observer.Length() == 0) {
DisableSwitchNotifications(aDevice);
ReleaseObserversIfNeeded();
}
}
void
NotifySwitchChange(const hal::SwitchEvent& aEvent)
{
// When callback this notification, main thread may call unregister function
// first. We should check if this pointer is valid.
if (!sSwitchObserverLists)
return;
SwitchObserverList& observer = GetSwitchObserverList(aEvent.device());
observer.Broadcast(aEvent);
}
} // namespace hal
} // namespace mozilla

View File

@ -345,6 +345,31 @@ bool LockScreenOrientation(const dom::ScreenOrientation& aOrientation);
*/
void UnlockScreenOrientation();
/**
* Register an observer for the switch of given SwitchDevice.
*
* The observer will receive data whenever the data generated by the
* given switch.
*/
void RegisterSwitchObserver(hal::SwitchDevice aDevice, hal::SwitchObserver *aSwitchObserver);
/**
* Unregister an observer for the switch of given SwitchDevice.
*/
void UnregisterSwitchObserver(hal::SwitchDevice aDevice, hal::SwitchObserver *aSwitchObserver);
/**
* Notify the state of the switch.
*
* This API is internal to hal; clients shouldn't call it directly.
*/
void NotifySwitchChange(const hal::SwitchEvent& aEvent);
/**
* Get current switch information.
*/
hal::SwitchState GetCurrentSwitchState(hal::SwitchDevice aDevice);
} // namespace MOZ_HAL_NAMESPACE
} // namespace mozilla

View File

@ -87,6 +87,16 @@ void EnableScreenOrientationNotifications();
*/
void DisableScreenOrientationNotifications();
/**
* Enable switch notifications from the backend
*/
void EnableSwitchNotifications(hal::SwitchDevice aDevice);
/**
* Disable switch notifications from the backend
*/
void DisableSwitchNotifications(hal::SwitchDevice aDevice);
} // namespace MOZ_HAL_NAMESPACE
} // namespace mozilla

View File

@ -37,6 +37,22 @@ enum FlashMode {
eHalLightFlash_Hardware = 2 // hardware assisted flashing
};
class SwitchEvent;
enum SwitchDevice {
SWITCH_DEVICE_UNKNOWN = -1,
SWITCH_HEADPHONES,
NUM_SWITCH_DEVICE
};
enum SwitchState {
SWITCH_STATE_UNKNOWN = -1,
SWITCH_STATE_ON,
SWITCH_STATE_OFF,
NUM_SWITCH_STATE
};
typedef Observer<SwitchEvent> SwitchObserver;
} // namespace hal
} // namespace mozilla
@ -97,6 +113,27 @@ struct ParamTraits<mozilla::hal::WakeLockControl>
mozilla::hal::WAKE_LOCK_ADD_ONE>
{};
/**
* Serializer for SwitchState
*/
template <>
struct ParamTraits<mozilla::hal::SwitchState>:
public EnumSerializer<mozilla::hal::SwitchState,
mozilla::hal::SWITCH_STATE_UNKNOWN,
mozilla::hal::NUM_SWITCH_STATE> {
};
/**
* Serializer for SwitchDevice
*/
template <>
struct ParamTraits<mozilla::hal::SwitchDevice>:
public EnumSerializer<mozilla::hal::SwitchDevice,
mozilla::hal::SWITCH_DEVICE_UNKNOWN,
mozilla::hal::NUM_SWITCH_DEVICE> {
};
} // namespace IPC
#endif // mozilla_hal_Types_h

View File

@ -85,6 +85,7 @@ CPPSRCS += \
Power.cpp \
GonkSensor.cpp \
UeventPoller.cpp \
GonkSwitch.cpp \
$(NULL)
else ifeq (Linux,$(OS_TARGET))
CPPSRCS += \
@ -121,6 +122,7 @@ CPPSRCS += \
FallbackLights.cpp \
FallbackTime.cpp \
FallbackWakeLocks.cpp \
FallbackSwitch.cpp \
$(NULL)
endif #}

View File

@ -0,0 +1,29 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/Hal.h"
using namespace mozilla::hal;
namespace mozilla {
namespace hal_impl {
void
EnableSwitchNotifications(SwitchDevice aDevice)
{
}
void
DisableSwitchNotifications(SwitchDevice aDevice)
{
}
SwitchState
GetCurrentSwitchState(SwitchDevice aDevice) {
return SWITCH_STATE_UNKNOWN;
}
} // namespace hal_impl
} // namespace mozilla

195
hal/gonk/GonkSwitch.cpp Normal file
View File

@ -0,0 +1,195 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <android/log.h>
#include <sysutils/NetlinkEvent.h>
#include "base/message_loop.h"
#include "Hal.h"
#include "nsXULAppAPI.h"
#include "UeventPoller.h"
using namespace mozilla::hal;
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GonkSwitch" , ## args)
namespace mozilla {
namespace hal_impl {
struct {const char* name; SwitchDevice device; } kSwitchNameMap[] = {
{ "h2w", SWITCH_HEADPHONES },
{ NULL, SWITCH_DEVICE_UNKNOWN },
};
static SwitchDevice
NameToDevice(const char* name) {
for (int i = 0; kSwitchNameMap[i].device != SWITCH_DEVICE_UNKNOWN; i++) {
if (strcmp(name, kSwitchNameMap[i].name) == 0) {
return kSwitchNameMap[i].device;
}
}
return SWITCH_DEVICE_UNKNOWN;
}
class SwitchEventRunnable : public nsRunnable
{
public:
SwitchEventRunnable(SwitchEvent& event) : mEvent(event) {}
NS_IMETHOD Run() {
NotifySwitchChange(mEvent);
return NS_OK;
}
private:
SwitchEvent mEvent;
};
class SwitchEventObserver : public IUeventObserver
{
public:
SwitchEventObserver() : mEnableNum(0) {
InternalInit();
}
~SwitchEventObserver() {}
int GetEnableCount() {
return mEnableNum;
}
void EnableSwitch(SwitchDevice aDevice) {
mEventInfo[aDevice].mEnable = true;
mEnableNum++;
}
void DisableSwitch(SwitchDevice aDevice) {
mEventInfo[aDevice].mEnable = false;
mEnableNum--;
}
void Notify(const NetlinkEvent& event) {
const char* name;
const char* state;
SwitchDevice device = ProcessEvent(event, &name, &state);
if (device == SWITCH_DEVICE_UNKNOWN) {
return;
}
EventInfo& info = mEventInfo[device];
info.mEvent.status() = atoi(state) == 0 ? SWITCH_STATE_OFF : SWITCH_STATE_ON;
if (info.mEnable) {
NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent));
}
}
SwitchState GetCurrentInformation(SwitchDevice aDevice) {
return mEventInfo[aDevice].mEvent.status();
}
private:
class EventInfo {
public:
EventInfo() : mEnable(false) {}
SwitchEvent mEvent;
bool mEnable;
};
EventInfo mEventInfo[NUM_SWITCH_DEVICE];
size_t mEnableNum;
void InternalInit() {
for (int i = 0; i < NUM_SWITCH_DEVICE; i++) {
mEventInfo[i].mEvent.device() = kSwitchNameMap[i].device;
mEventInfo[i].mEvent.status() = SWITCH_STATE_UNKNOWN;
}
}
bool GetEventInfo(const NetlinkEvent& event, const char** name, const char** state) {
//working around the android code not being const-correct
NetlinkEvent *e = const_cast<NetlinkEvent*>(&event);
const char* subsystem = e->getSubsystem();
if (!subsystem || strcmp(subsystem, "switch")) {
return false;
}
*name = e->findParam("SWITCH_NAME");
*state = e->findParam("SWITCH_STATE");
if (!*name || !*state) {
return false;
}
return true;
}
SwitchDevice ProcessEvent(const NetlinkEvent& event, const char** name, const char** state) {
bool rv = GetEventInfo(event, name, state);
NS_ENSURE_TRUE(rv, SWITCH_DEVICE_UNKNOWN);
return NameToDevice(*name);
}
};
SwitchEventObserver* sSwitchObserver;
static void
InitializeResourceIfNeed()
{
if (!sSwitchObserver) {
sSwitchObserver = new SwitchEventObserver();
RegisterUeventListener(sSwitchObserver);
}
}
static void
ReleaseResourceIfNeed()
{
if (sSwitchObserver->GetEnableCount() == 0) {
UnregisterUeventListener(sSwitchObserver);
delete sSwitchObserver;
sSwitchObserver = NULL;
}
}
static void
EnableSwitchNotificationsIOThread(SwitchDevice aDevice)
{
InitializeResourceIfNeed();
sSwitchObserver->EnableSwitch(aDevice);
}
void
EnableSwitchNotifications(SwitchDevice aDevice)
{
XRE_GetIOMessageLoop()->PostTask(
FROM_HERE,
NewRunnableFunction(EnableSwitchNotificationsIOThread, aDevice));
}
static void
DisableSwitchNotificationsIOThread(SwitchDevice aDevice)
{
MOZ_ASSERT(sSwitchObserver->GetEnableCount());
sSwitchObserver->DisableSwitch(aDevice);
ReleaseResourceIfNeed();
}
void
DisableSwitchNotifications(SwitchDevice aDevice)
{
XRE_GetIOMessageLoop()->PostTask(
FROM_HERE,
NewRunnableFunction(DisableSwitchNotificationsIOThread, aDevice));
}
SwitchState
GetCurrentSwitchState(SwitchDevice aDevice)
{
MOZ_ASSERT(sSwitchObserver && sSwitchObserver->GetEnableCount());
return sSwitchObserver->GetCurrentInformation(aDevice);
}
} // hal_impl
} //mozilla

View File

@ -52,6 +52,8 @@ using mozilla::hal::SensorType;
using mozilla::hal::SensorAccuracyType;
using mozilla::hal::WakeLockControl;
using mozilla::dom::ScreenOrientation;
using mozilla::hal::SwitchState;
using mozilla::hal::SwitchDevice;
namespace mozilla {
@ -82,6 +84,11 @@ namespace hal {
double bandwidth;
bool canBeMetered;
};
struct SwitchEvent {
SwitchDevice device;
SwitchState status;
};
}
namespace hal {
@ -102,6 +109,7 @@ child:
NotifyNetworkChange(NetworkInformation aNetworkInfo);
NotifyWakeLockChange(WakeLockInformation aWakeLockInfo);
NotifyScreenOrientationChange(ScreenOrientation aScreenOrientation);
NotifySwitchChange(SwitchEvent aEvent);
parent:
Vibrate(uint32[] pattern, uint64[] id, PBrowser browser);
@ -150,6 +158,11 @@ parent:
sync LockScreenOrientation(ScreenOrientation aOrientation)
returns (bool allowed);
UnlockScreenOrientation();
EnableSwitchNotifications(SwitchDevice aDevice);
DisableSwitchNotifications(SwitchDevice aDevice);
sync GetCurrentSwitchState(SwitchDevice aDevice)
returns (SwitchState aState);
child:
NotifySensorChange(SensorData aSensorData);

View File

@ -240,12 +240,33 @@ GetWakeLockInfo(const nsAString &aTopic, WakeLockInformation *aWakeLockInfo)
Hal()->SendGetWakeLockInfo(nsString(aTopic), aWakeLockInfo);
}
void
EnableSwitchNotifications(SwitchDevice aDevice)
{
Hal()->SendEnableSwitchNotifications(aDevice);
}
void
DisableSwitchNotifications(SwitchDevice aDevice)
{
Hal()->SendDisableSwitchNotifications(aDevice);
}
SwitchState
GetCurrentSwitchState(SwitchDevice aDevice)
{
SwitchState state;
Hal()->SendGetCurrentSwitchState(aDevice, &state);
return state;
}
class HalParent : public PHalParent
, public BatteryObserver
, public NetworkObserver
, public ISensorObserver
, public WakeLockObserver
, public ScreenOrientationObserver
, public SwitchObserver
{
public:
NS_OVERRIDE virtual bool
@ -501,6 +522,32 @@ public:
{
unused << SendNotifyWakeLockChange(aWakeLockInfo);
}
NS_OVERRIDE virtual bool
RecvEnableSwitchNotifications(const SwitchDevice& aDevice)
{
hal::RegisterSwitchObserver(aDevice, this);
return true;
}
NS_OVERRIDE virtual bool
RecvDisableSwitchNotifications(const SwitchDevice& aDevice)
{
hal::UnregisterSwitchObserver(aDevice, this);
return true;
}
void Notify(const SwitchEvent& aSwitchEvent)
{
unused << SendNotifySwitchChange(aSwitchEvent);
}
NS_OVERRIDE virtual bool
RecvGetCurrentSwitchState(const SwitchDevice& aDevice, hal::SwitchState *aState)
{
*aState = hal::GetCurrentSwitchState(aDevice);
return true;
}
};
class HalChild : public PHalChild {
@ -531,6 +578,12 @@ public:
hal::NotifyScreenOrientationChange(aScreenOrientation);
return true;
}
NS_OVERRIDE virtual bool
RecvNotifySwitchChange(const mozilla::hal::SwitchEvent& aEvent) {
hal::NotifySwitchChange(aEvent);
return true;
}
};
bool

View File

@ -60,8 +60,10 @@ nsresult xpcJSWeakReference::Init(JSContext* cx, const JS::Value& object)
// See if the object is a wrapped native that supports weak references.
nsISupports* supports =
nsXPConnect::GetXPConnect()->GetNativeOfWrapper(cx, &obj);
if (supports) {
mReferent = do_GetWeakReference(supports);
nsCOMPtr<nsISupportsWeakReference> supportsWeakRef =
do_QueryInterface(supports);
if (supportsWeakRef) {
supportsWeakRef->GetWeakReference(getter_AddRefs(mReferent));
if (mReferent) {
return NS_OK;
}

View File

@ -511,11 +511,16 @@ function OnDocumentLoad(event)
}
var contentRootElement = currentDoc ? currentDoc.documentElement : null;
currentDoc = null;
setupZoom(contentRootElement);
setupDisplayport(contentRootElement);
var inPrintMode = false;
function AfterOnLoadScripts() {
// Regrab the root element, because the document may have changed.
var contentRootElement =
content.document ? content.document.documentElement : null;
// Take a snapshot now. We need to do this before we check whether
// we should wait, since this might trigger dispatching of
// MozPaintWait events and make shouldWaitForExplicitPaintWaiters() true
@ -671,7 +676,6 @@ function LogInfo(str)
const SYNC_DEFAULT = 0x0;
const SYNC_ALLOW_DISABLE = 0x1;
var gDummyCanvas = null;
function SynchronizeForSnapshot(flags)
{
if (flags & SYNC_ALLOW_DISABLE) {
@ -682,13 +686,11 @@ function SynchronizeForSnapshot(flags)
}
}
if (gDummyCanvas == null) {
gDummyCanvas = content.document.createElementNS(XHTML_NS, "canvas");
gDummyCanvas.setAttribute("width", 1);
gDummyCanvas.setAttribute("height", 1);
}
var dummyCanvas = content.document.createElementNS(XHTML_NS, "canvas");
dummyCanvas.setAttribute("width", 1);
dummyCanvas.setAttribute("height", 1);
var ctx = gDummyCanvas.getContext("2d");
var ctx = dummyCanvas.getContext("2d");
var flags = ctx.DRAWWINDOW_DRAW_CARET | ctx.DRAWWINDOW_DRAW_VIEW | ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
ctx.drawWindow(content, 0, 0, 1, 1, "rgb(255,255,255)", flags);
}

View File

@ -405,9 +405,9 @@ void *_mmap(void *addr, size_t length, int prot, int flags,
struct {
void *addr;
size_t length;
int prot;
int flags;
int fd;
long prot;
long flags;
long fd;
off_t offset;
} args = { addr, length, prot, flags, fd, offset };
return (void *) syscall(SYS_mmap, &args);

View File

@ -124,6 +124,9 @@ public class ProfileMigrator {
private static final String ROOT_NAME = "root_name";
private static final String ROOT_FOLDER_ID = "folder_id";
// We use this to ignore the tags folder during migration.
private static final String ROOT_TAGS_FOLDER_NAME = "tags";
private static final String BOOKMARK_QUERY_SELECT =
"SELECT places.url AS p_url," +
" bookmark.guid AS b_guid," +
@ -557,6 +560,7 @@ public class ProfileMigrator {
private class PlacesRunnable implements Runnable {
private Map<Long, Long> mRerootMap;
private Long mTagsPlacesFolderId;
private ArrayList<ContentProviderOperation> mOperations;
private int mMaxEntries;
// We support 2 classes of schemas: Firefox Places 12-13
@ -644,6 +648,12 @@ public class ProfileMigrator {
mRerootMap.put(placesFolderId, getFolderId(name));
Log.v(LOGTAG, "Name: " + name + ", pid=" + placesFolderId
+ ", nid=" + mRerootMap.get(placesFolderId));
// Keep track of the tags folder id so we can avoid
// migrating tags later.
if (ROOT_TAGS_FOLDER_NAME.equals(name))
mTagsPlacesFolderId = placesFolderId;
cursor.moveToNext();
}
cursor.close();
@ -1051,9 +1061,10 @@ public class ProfileMigrator {
int type = cursor.getInt(typeCol);
long parent = cursor.getLong(parentCol);
// Places has an explicit root folder, id=1 parent=0.
// Skip that.
if (id == 1 && parent == 0 && type == PLACES_TYPE_FOLDER) {
// Places has an explicit root folder, id=1 parent=0. Skip that.
// Also, skip tags, since we don't use those in native fennec.
if ((id == 1 && parent == 0 && type == PLACES_TYPE_FOLDER) ||
parent == mTagsPlacesFolderId) {
cursor.moveToNext();
continue;
}

View File

@ -0,0 +1,15 @@
/* 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.sync;
public class EngineSettings {
public final String syncID;
public final int version;
public EngineSettings(final String syncID, final int version) {
this.syncID = syncID;
this.version = version;
}
}

View File

@ -131,6 +131,30 @@ public class ExtendedJSONObject {
return (String) this.get(key);
}
/**
* Return an Integer if the value for this key is an Integer, Long, or String
* that can be parsed as a base 10 Integer.
* Passes through null.
*
* @throws NumberFormatException
*/
public Integer getIntegerSafely(String key) throws NumberFormatException {
Object val = this.object.get(key);
if (val == null) {
return null;
}
if (val instanceof Integer) {
return (Integer) val;
}
if (val instanceof Long) {
return new Integer(((Long) val).intValue());
}
if (val instanceof String) {
return Integer.parseInt((String) val, 10);
}
throw new NumberFormatException("Expecting Integer, got " + val.getClass());
}
/**
* Return a server timestamp value as milliseconds since epoch.
* @param key

View File

@ -8,7 +8,11 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
@ -31,9 +35,7 @@ import org.mozilla.gecko.sync.net.SyncStorageRequest;
import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import org.mozilla.gecko.sync.stage.AndroidBrowserBookmarksServerSyncStage;
import org.mozilla.gecko.sync.stage.FormHistoryServerSyncStage;
import org.mozilla.gecko.sync.stage.AndroidBrowserHistoryServerSyncStage;
import org.mozilla.gecko.sync.stage.PasswordsServerSyncStage;
import org.mozilla.gecko.sync.stage.CheckPreconditionsStage;
import org.mozilla.gecko.sync.stage.CompletedStage;
import org.mozilla.gecko.sync.stage.EnsureClusterURLStage;
@ -41,9 +43,11 @@ import org.mozilla.gecko.sync.stage.EnsureCrypto5KeysStage;
import org.mozilla.gecko.sync.stage.FennecTabsServerSyncStage;
import org.mozilla.gecko.sync.stage.FetchInfoCollectionsStage;
import org.mozilla.gecko.sync.stage.FetchMetaGlobalStage;
import org.mozilla.gecko.sync.stage.FormHistoryServerSyncStage;
import org.mozilla.gecko.sync.stage.GlobalSyncStage;
import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
import org.mozilla.gecko.sync.stage.NoSuchStageException;
import org.mozilla.gecko.sync.stage.PasswordsServerSyncStage;
import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
import android.content.Context;
@ -152,6 +156,8 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
registerCommands();
prepareStages();
// TODO: data-driven plan for the sync, referring to prepareStages.
}
protected void registerCommands() {
@ -160,14 +166,32 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
processor.registerCommand("resetEngine", new CommandRunner() {
@Override
public void executeCommand(List<String> args) {
resetClient(new String[] { args.get(0) });
HashSet<String> names = new HashSet<String>();
names.add(args.get(0));
resetStagesByName(names);
}
});
processor.registerCommand("resetAll", new CommandRunner() {
@Override
public void executeCommand(List<String> args) {
resetClient(null);
resetAllStages();
}
});
processor.registerCommand("wipeEngine", new CommandRunner() {
@Override
public void executeCommand(List<String> args) {
HashSet<String> names = new HashSet<String>();
names.add(args.get(0));
wipeStagesByName(names);
}
});
processor.registerCommand("wipeAll", new CommandRunner() {
@Override
public void executeCommand(List<String> args) {
wipeAllStages();
}
});
@ -180,24 +204,31 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
}
protected void prepareStages() {
stages = new HashMap<Stage, GlobalSyncStage>();
stages.put(Stage.checkPreconditions, new CheckPreconditionsStage());
stages.put(Stage.ensureClusterURL, new EnsureClusterURLStage());
stages.put(Stage.fetchInfoCollections, new FetchInfoCollectionsStage());
stages.put(Stage.fetchMetaGlobal, new FetchMetaGlobalStage());
stages.put(Stage.ensureKeysStage, new EnsureCrypto5KeysStage());
stages.put(Stage.syncClientsEngine, new SyncClientsEngineStage());
HashMap<Stage, GlobalSyncStage> stages = new HashMap<Stage, GlobalSyncStage>();
// TODO: more stages.
stages.put(Stage.syncTabs, new FennecTabsServerSyncStage());
stages.put(Stage.syncPasswords, new PasswordsServerSyncStage());
stages.put(Stage.syncBookmarks, new AndroidBrowserBookmarksServerSyncStage());
stages.put(Stage.syncHistory, new AndroidBrowserHistoryServerSyncStage());
stages.put(Stage.syncFormHistory, new FormHistoryServerSyncStage());
stages.put(Stage.completed, new CompletedStage());
stages.put(Stage.checkPreconditions, new CheckPreconditionsStage(this));
stages.put(Stage.ensureClusterURL, new EnsureClusterURLStage(this));
stages.put(Stage.fetchInfoCollections, new FetchInfoCollectionsStage(this));
stages.put(Stage.fetchMetaGlobal, new FetchMetaGlobalStage(this));
stages.put(Stage.ensureKeysStage, new EnsureCrypto5KeysStage(this));
stages.put(Stage.syncClientsEngine, new SyncClientsEngineStage(this));
stages.put(Stage.syncTabs, new FennecTabsServerSyncStage(this));
stages.put(Stage.syncPasswords, new PasswordsServerSyncStage(this));
stages.put(Stage.syncBookmarks, new AndroidBrowserBookmarksServerSyncStage(this));
stages.put(Stage.syncHistory, new AndroidBrowserHistoryServerSyncStage(this));
stages.put(Stage.syncFormHistory, new FormHistoryServerSyncStage(this));
stages.put(Stage.completed, new CompletedStage(this));
this.stages = Collections.unmodifiableMap(stages);
}
protected GlobalSyncStage getStageByName(Stage next) throws NoSuchStageException {
public GlobalSyncStage getSyncStageByName(String name) throws NoSuchStageException {
return getSyncStageByName(Stage.byName(name));
}
public GlobalSyncStage getSyncStageByName(Stage next) throws NoSuchStageException {
GlobalSyncStage stage = stages.get(next);
if (stage == null) {
throw new NoSuchStageException(next);
@ -205,6 +236,32 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
return stage;
}
public Collection<GlobalSyncStage> getSyncStagesByEnum(Collection<Stage> enums) {
ArrayList<GlobalSyncStage> out = new ArrayList<GlobalSyncStage>();
for (Stage name : enums) {
try {
GlobalSyncStage stage = this.getSyncStageByName(name);
out.add(stage);
} catch (NoSuchStageException e) {
Logger.warn(LOG_TAG, "Unable to find stage with name " + name);
}
}
return out;
}
public Collection<GlobalSyncStage> getSyncStagesByName(Collection<String> names) {
ArrayList<GlobalSyncStage> out = new ArrayList<GlobalSyncStage>();
for (String name : names) {
try {
GlobalSyncStage stage = this.getSyncStageByName(name);
out.add(stage);
} catch (NoSuchStageException e) {
Logger.warn(LOG_TAG, "Unable to find stage with name " + name);
}
}
return out;
}
/**
* Advance and loop around the stages of a sync.
* @param current
@ -232,7 +289,7 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
Stage next = nextStage(this.currentState);
GlobalSyncStage nextStage;
try {
nextStage = this.getStageByName(next);
nextStage = this.getSyncStageByName(next);
} catch (NoSuchStageException e) {
this.abort(e, "No such stage " + next);
return;
@ -240,7 +297,7 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
this.currentState = next;
Logger.info(LOG_TAG, "Running next stage " + next + " (" + nextStage + ")...");
try {
nextStage.execute(this);
nextStage.execute();
} catch (Exception ex) {
Logger.warn(LOG_TAG, "Caught exception " + ex + " running stage " + next);
this.abort(ex, "Uncaught exception in stage.");
@ -435,10 +492,9 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
String localSyncID = this.getSyncID();
if (!remoteSyncID.equals(localSyncID)) {
// Sync ID has changed. Reset timestamps and fetch new keys.
resetClient(null);
resetAllStages();
config.purgeCryptoKeys();
config.syncID = remoteSyncID;
// TODO TODO TODO
}
config.persistToPrefs();
advance();
@ -486,7 +542,7 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
@Override
public void onWiped(long timestamp) {
session.resetClient(null);
session.resetAllStages();
session.config.purgeCryptoKeys();
session.config.persistToPrefs();
@ -558,9 +614,26 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
freshStartDelegate.onFreshStartFailed(e);
}
});
}
// Note that we do not yet implement wipeRemote: it's only necessary for
// first sync options.
// -- reset local stages, wipe server for each stage *except* clients
// (stages only, not whole server!), send wipeEngine commands to each client.
//
// Similarly for startOver (because we don't receive that notification).
// -- remove client data from server, reset local stages, clear keys, reset
// backoff, clear all prefs, discard credentials.
//
// Change passphrase: wipe entire server, reset client to force upload, sync.
//
// When an engine is disabled: wipe its collections on the server, reupload
// meta/global.
//
// On syncing each stage: if server has engine version 0 or old, wipe server,
// reset client to prompt reupload.
// If sync ID mismatch: take that syncID and reset client.
private void wipeServer(final CredentialsSource credentials, final WipeServerDelegate wipeDelegate) {
SyncStorageRequest request;
final GlobalSession self = this;
@ -609,16 +682,62 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
request.delete();
}
/**
* Reset our state. Clear our sync ID, reset each engine, drop any
* cached records.
*/
private void resetClient(String[] engines) {
if (engines == null) {
// Set `engines` to be *all* the engines.
public void wipeAllStages() {
Logger.info(LOG_TAG, "Wiping all stages.");
// Includes "clients".
this.wipeStagesByEnum(Stage.getNamedStages());
}
public static void wipeStages(Collection<GlobalSyncStage> stages) {
for (GlobalSyncStage stage : stages) {
try {
Logger.info(LOG_TAG, "Wiping " + stage);
stage.wipeLocal();
} catch (Exception e) {
Logger.error(LOG_TAG, "Ignoring wipe failure for stage " + stage, e);
}
}
}
public void wipeStagesByEnum(Collection<Stage> stages) {
GlobalSession.wipeStages(this.getSyncStagesByEnum(stages));
}
public void wipeStagesByName(Collection<String> names) {
GlobalSession.wipeStages(this.getSyncStagesByName(names));
}
public void resetAllStages() {
Logger.info(LOG_TAG, "Resetting all stages.");
// Includes "clients".
this.resetStagesByEnum(Stage.getNamedStages());
}
public static void resetStages(Collection<GlobalSyncStage> stages) {
for (GlobalSyncStage stage : stages) {
try {
Logger.info(LOG_TAG, "Resetting " + stage);
stage.resetLocal();
} catch (Exception e) {
Logger.error(LOG_TAG, "Ignoring reset failure for stage " + stage, e);
}
}
}
public void resetStagesByEnum(Collection<Stage> stages) {
GlobalSession.resetStages(this.getSyncStagesByEnum(stages));
}
public void resetStagesByName(Collection<String> names) {
for (String name : names) {
try {
GlobalSyncStage stage = this.getSyncStageByName(name);
Logger.info(LOG_TAG, "Resetting " + name + "(" + stage + ")");
stage.resetLocal();
} catch (NoSuchStageException e) {
Logger.warn(LOG_TAG, "Cannot reset stage " + name + ": no such stage.");
}
}
// TODO: futz with config?!
// TODO: engines?!
}
/**
@ -635,21 +754,40 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
* Otherwise, returns true if there is an entry for this engine in the
* meta/global "engines" object.
*
* @param engineName
* @param engineName the name to check (e.g., "bookmarks").
* @param engineSettings
* if non-null, verify that the server engine settings are congruent
* with this, throwing the appropriate MetaGlobalException if not.
* @return
* true if the engine with the provided name is present in the
* meta/global "engines" object.
* meta/global "engines" object, and verification passed.
*
* @throws MetaGlobalException
*/
public boolean engineIsEnabled(String engineName) throws MetaGlobalException {
public boolean engineIsEnabled(String engineName, EngineSettings engineSettings) throws MetaGlobalException {
if (this.config.metaGlobal == null) {
throw new MetaGlobalNotSetException();
}
if (this.config.metaGlobal.engines == null) {
throw new MetaGlobalMissingEnginesException();
}
return this.config.metaGlobal.engines.get(engineName) != null;
ExtendedJSONObject engineEntry;
try {
engineEntry = this.config.metaGlobal.engines.getObject(engineName);
} catch (NonObjectJSONException e) {
Logger.error(LOG_TAG, "Engine field for " + engineName + " in meta/global is not an object.");
throw new MetaGlobalMissingEnginesException();
}
if (engineEntry == null) {
Logger.debug(LOG_TAG, "Engine " + engineName + " not enabled: no meta/global entry.");
return false;
}
if (engineSettings != null) {
MetaGlobal.verifyEngineSettings(engineEntry, engineSettings);
}
return true;
}
public ClientsDataDelegate getClientsDelegate() {

View File

@ -8,6 +8,10 @@ import java.io.IOException;
import java.net.URISyntaxException;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.sync.MetaGlobalException.MetaGlobalMalformedSyncIDException;
import org.mozilla.gecko.sync.MetaGlobalException.MetaGlobalMalformedVersionException;
import org.mozilla.gecko.sync.MetaGlobalException.MetaGlobalStaleClientSyncIDException;
import org.mozilla.gecko.sync.MetaGlobalException.MetaGlobalStaleClientVersionException;
import org.mozilla.gecko.sync.delegates.MetaGlobalDelegate;
import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
@ -105,6 +109,55 @@ public class MetaGlobal implements SyncStorageRequestDelegate {
this.engines = engines;
}
/**
* Returns if the server settings and local settings match.
* Throws a specific exception if that's not the case.
*/
public static void verifyEngineSettings(ExtendedJSONObject engineEntry,
EngineSettings engineSettings)
throws MetaGlobalMalformedVersionException, MetaGlobalMalformedSyncIDException, MetaGlobalStaleClientVersionException, MetaGlobalStaleClientSyncIDException {
if (engineEntry == null) {
throw new IllegalArgumentException("engineEntry cannot be null.");
}
if (engineSettings == null) {
throw new IllegalArgumentException("engineSettings cannot be null.");
}
try {
Integer version = engineEntry.getIntegerSafely("version");
if (version == null ||
version.intValue() == 0) {
// Invalid version. Wipe the server.
throw new MetaGlobalException.MetaGlobalMalformedVersionException();
}
if (version > engineSettings.version) {
// We're out of date.
throw new MetaGlobalException.MetaGlobalStaleClientVersionException(version);
}
try {
String syncID = engineEntry.getString("syncID");
if (syncID == null) {
// No syncID. This should never happen. Wipe the server.
throw new MetaGlobalException.MetaGlobalMalformedSyncIDException();
}
if (!syncID.equals(engineSettings.syncID)) {
// Our syncID is wrong. Reset client and take the server syncID.
throw new MetaGlobalException.MetaGlobalStaleClientSyncIDException(syncID);
}
// Great!
return;
} catch (ClassCastException e) {
// Malformed syncID on the server. Wipe the server.
throw new MetaGlobalException.MetaGlobalMalformedSyncIDException();
}
} catch (NumberFormatException e) {
// Invalid version. Wipe the server.
throw new MetaGlobalException.MetaGlobalMalformedVersionException();
}
}
public String getSyncID() {
return syncID;
}

View File

@ -1,42 +1,37 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Android Sync Client.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Richard Newman <rnewman@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* 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.sync;
public class MetaGlobalException extends SyncException {
private static final long serialVersionUID = -6182315615113508925L;
public static class MetaGlobalMalformedSyncIDException extends MetaGlobalException {
private static final long serialVersionUID = 1L;
}
public static class MetaGlobalMalformedVersionException extends MetaGlobalException {
private static final long serialVersionUID = 1L;
}
public static class MetaGlobalOutdatedVersionException extends MetaGlobalException {
private static final long serialVersionUID = 1L;
}
public static class MetaGlobalStaleClientVersionException extends MetaGlobalException {
private static final long serialVersionUID = 1L;
public final int serverVersion;
public MetaGlobalStaleClientVersionException(final int version) {
this.serverVersion = version;
}
}
public static class MetaGlobalStaleClientSyncIDException extends MetaGlobalException {
private static final long serialVersionUID = 1L;
public final String serverSyncID;
public MetaGlobalStaleClientSyncIDException(final String syncID) {
this.serverSyncID = syncID;
}
}
}

View File

@ -431,6 +431,12 @@ public class AndroidBrowserBookmarksRepositorySession extends AndroidBrowserRepo
String parentName = getParentName(androidParentGUID);
BookmarkRecord bookmark = AndroidBrowserBookmarksRepositorySession.bookmarkFromMirrorCursor(cur, androidParentGUID, parentName, childArray);
if (bookmark == null) {
Logger.warn(LOG_TAG, "Unable to extract bookmark from cursor. Record GUID " + recordGUID +
", parent " + androidParentGUID + "/" + androidParentID);
return null;
}
if (needsReparenting) {
Logger.warn(LOG_TAG, "Bookmark record " + recordGUID + " has a bad parent pointer. Reparenting now.");
@ -786,13 +792,13 @@ public class AndroidBrowserBookmarksRepositorySession extends AndroidBrowserRepo
@Override
public void run() {
try {
// Clear our queued deletions.
deletionManager.clear();
super.run();
} catch (Exception ex) {
delegate.onWipeFailed(ex);
return;
}
// Clear our queued deletions.
deletionManager.clear();
}
}
@ -908,6 +914,8 @@ public class AndroidBrowserBookmarksRepositorySession extends AndroidBrowserRepo
if (typeString == null) {
Logger.warn(LOG_TAG, "Unsupported type code " + rowType);
return null;
} else {
Logger.trace(LOG_TAG, "Record " + guid + " has type " + typeString);
}
rec.type = typeString;

View File

@ -0,0 +1,28 @@
/* 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.sync.stage;
import org.mozilla.gecko.sync.GlobalSession;
/**
* This is simply a stage that is not responsible for synchronizing repositories.
*/
public abstract class AbstractNonRepositorySyncStage implements GlobalSyncStage {
protected final GlobalSession session;
public AbstractNonRepositorySyncStage(GlobalSession session) {
this.session = session;
}
@Override
public void resetLocal() {
// Do nothing.
}
@Override
public void wipeLocal() {
// Do nothing.
}
}

View File

@ -1,44 +1,12 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Android Sync Client.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Richard Newman <rnewman@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* 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.sync.stage;
import java.net.URISyntaxException;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.Logger;
import org.mozilla.gecko.sync.MetaGlobalException;
import org.mozilla.gecko.sync.repositories.ConstrainedServer11Repository;
@ -56,6 +24,10 @@ public class AndroidBrowserBookmarksServerSyncStage extends ServerSyncStage {
private static final String BOOKMARKS_SORT = "index";
private static final long BOOKMARKS_REQUEST_LIMIT = 5000; // Sanity limit.
public AndroidBrowserBookmarksServerSyncStage(GlobalSession session) {
super(session);
}
@Override
protected String getCollection() {
return "bookmarks";
@ -87,7 +59,7 @@ public class AndroidBrowserBookmarksServerSyncStage extends ServerSyncStage {
@Override
protected boolean isEnabled() throws MetaGlobalException {
if (session == null || session.getContext() == null) {
if (session.getContext() == null) {
return false;
}
boolean migrated = FennecControlHelper.areBookmarksMigrated(session.getContext());

View File

@ -39,6 +39,7 @@ package org.mozilla.gecko.sync.stage;
import java.net.URISyntaxException;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.Logger;
import org.mozilla.gecko.sync.MetaGlobalException;
import org.mozilla.gecko.sync.repositories.ConstrainedServer11Repository;
@ -56,6 +57,10 @@ public class AndroidBrowserHistoryServerSyncStage extends ServerSyncStage {
private static final String HISTORY_SORT = "index";
private static final long HISTORY_REQUEST_LIMIT = 250;
public AndroidBrowserHistoryServerSyncStage(GlobalSession session) {
super(session);
}
@Override
protected String getCollection() {
return "history";

View File

@ -1,46 +1,18 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Android Sync Client.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Richard Newman <rnewman@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* 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.sync.stage;
import org.mozilla.gecko.sync.GlobalSession;
public class CheckPreconditionsStage implements GlobalSyncStage {
public void execute(GlobalSession session) throws NoSuchStageException {
public class CheckPreconditionsStage extends AbstractNonRepositorySyncStage {
public CheckPreconditionsStage(GlobalSession session) {
super(session);
}
@Override
public void execute() throws NoSuchStageException {
session.advance();
}
}

View File

@ -1,51 +1,22 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Android Sync Client.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Richard Newman <rnewman@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* 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.sync.stage;
import org.mozilla.gecko.sync.GlobalSession;
public class CompletedStage implements GlobalSyncStage {
public class CompletedStage extends AbstractNonRepositorySyncStage {
public CompletedStage(GlobalSession session) {
super(session);
}
@Override
public void execute(GlobalSession session) throws NoSuchStageException {
public void execute() throws NoSuchStageException {
// TODO: Update tracking timestamps, close connections, etc.
// TODO: call clean() on each Repository in the sync constellation.
session.completeSync();
}
}

View File

@ -24,7 +24,11 @@ import ch.boye.httpclientandroidlib.HttpEntity;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
public class EnsureClusterURLStage implements GlobalSyncStage {
public class EnsureClusterURLStage extends AbstractNonRepositorySyncStage {
public EnsureClusterURLStage(GlobalSession session) {
super(session);
}
public interface ClusterURLFetchDelegate {
/**
* 200 - Success.
@ -172,7 +176,7 @@ public class EnsureClusterURLStage implements GlobalSyncStage {
resource.get();
}
public void execute(final GlobalSession session) throws NoSuchStageException {
public void execute() throws NoSuchStageException {
final URI oldClusterURL = session.config.getClusterURL();
final boolean wantNodeAssignment = session.callback.wantNodeAssignment();

View File

@ -23,16 +23,20 @@ import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
public class EnsureCrypto5KeysStage implements GlobalSyncStage, SyncStorageRequestDelegate, KeyUploadDelegate {
public class EnsureCrypto5KeysStage
extends AbstractNonRepositorySyncStage
implements SyncStorageRequestDelegate, KeyUploadDelegate {
public EnsureCrypto5KeysStage(GlobalSession session) {
super(session);
}
private static final String LOG_TAG = "EnsureC5KeysStage";
private static final String CRYPTO_COLLECTION = "crypto";
protected GlobalSession session;
protected boolean retrying = false;
@Override
public void execute(GlobalSession session) throws NoSuchStageException {
this.session = session;
public void execute() throws NoSuchStageException {
InfoCollections infoCollections = session.config.infoCollections;
if (infoCollections == null) {
session.abort(null, "No info/collections set in EnsureCrypto5KeysStage.");
@ -161,7 +165,7 @@ public class EnsureCrypto5KeysStage implements GlobalSyncStage, SyncStorageReque
Logger.debug(LOG_TAG, "New keys uploaded. Starting stage again to fetch them.");
try {
retrying = true;
this.execute(this.session);
this.execute();
} catch (NoSuchStageException e) {
session.abort(e, "No such stage.");
}

View File

@ -6,6 +6,7 @@ package org.mozilla.gecko.sync.stage;
import org.mozilla.gecko.sync.repositories.domain.TabsRecord;
import org.mozilla.gecko.sync.CryptoRecord;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.repositories.RecordFactory;
import org.mozilla.gecko.sync.repositories.Repository;
import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
@ -14,6 +15,10 @@ import org.mozilla.gecko.sync.repositories.domain.Record;
public class FennecTabsServerSyncStage extends ServerSyncStage {
private static final String COLLECTION = "tabs";
public FennecTabsServerSyncStage(GlobalSession session) {
super(session);
}
public class FennecTabsRecordFactory extends RecordFactory {
@Override
public Record createRecord(Record record) {

View File

@ -1,39 +1,6 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Android Sync Client.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Richard Newman <rnewman@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* 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.sync.stage;
@ -46,15 +13,14 @@ import org.mozilla.gecko.sync.net.SyncStorageResponse;
import android.util.Log;
public class FetchInfoCollectionsStage implements GlobalSyncStage {
public class FetchInfoCollectionsStage extends AbstractNonRepositorySyncStage {
private static final String LOG_TAG = "FetchInfoCollStage";
public class StageInfoCollectionsDelegate implements InfoCollectionsDelegate {
public FetchInfoCollectionsStage(GlobalSession session) {
super(session);
}
private GlobalSession session;
public StageInfoCollectionsDelegate(GlobalSession session) {
this.session = session;
}
public class StageInfoCollectionsDelegate implements InfoCollectionsDelegate {
@Override
public void handleSuccess(InfoCollections global) {
@ -77,9 +43,9 @@ public class FetchInfoCollectionsStage implements GlobalSyncStage {
}
@Override
public void execute(GlobalSession session) throws NoSuchStageException {
public void execute() throws NoSuchStageException {
try {
session.fetchInfoCollections(new StageInfoCollectionsDelegate(session));
session.fetchInfoCollections(new StageInfoCollectionsDelegate());
} catch (URISyntaxException e) {
session.abort(e, "Invalid URI.");
}

View File

@ -12,7 +12,11 @@ import org.mozilla.gecko.sync.PersistedMetaGlobal;
import org.mozilla.gecko.sync.delegates.MetaGlobalDelegate;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
public class FetchMetaGlobalStage implements GlobalSyncStage {
public class FetchMetaGlobalStage extends AbstractNonRepositorySyncStage {
public FetchMetaGlobalStage(GlobalSession session) {
super(session);
}
private static final String LOG_TAG = "FetchMetaGlobalStage";
private static final String META_COLLECTION = "meta";
@ -51,7 +55,7 @@ public class FetchMetaGlobalStage implements GlobalSyncStage {
}
@Override
public void execute(GlobalSession session) throws NoSuchStageException {
public void execute() throws NoSuchStageException {
InfoCollections infoCollections = session.config.infoCollections;
if (infoCollections == null) {
session.abort(null, "No info/collections set in FetchMetaGlobalStage.");

View File

@ -7,6 +7,7 @@ package org.mozilla.gecko.sync.stage;
import java.net.URISyntaxException;
import org.mozilla.gecko.sync.CryptoRecord;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.repositories.ConstrainedServer11Repository;
import org.mozilla.gecko.sync.repositories.RecordFactory;
import org.mozilla.gecko.sync.repositories.Repository;
@ -21,9 +22,13 @@ public class FormHistoryServerSyncStage extends ServerSyncStage {
private static final String FORM_HISTORY_SORT = "index";
private static final long FORM_HISTORY_REQUEST_LIMIT = 5000; // Sanity limit.
public FormHistoryServerSyncStage(GlobalSession session) {
super(session);
}
@Override
public void execute(org.mozilla.gecko.sync.GlobalSession session) throws NoSuchStageException {
super.execute(session);
public void execute() throws NoSuchStageException {
super.execute();
}
@Override

View File

@ -4,7 +4,12 @@
package org.mozilla.gecko.sync.stage;
import org.mozilla.gecko.sync.GlobalSession;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
public interface GlobalSyncStage {
public static enum Stage {
@ -18,19 +23,59 @@ public interface GlobalSyncStage {
ensureSpecialRecords,
updateEngineTimestamps,
*/
syncClientsEngine,
syncClientsEngine("clients"),
/*
processFirstSyncPref,
processClientCommands,
updateEnabledEngines,
*/
syncTabs,
syncPasswords,
syncBookmarks,
syncHistory,
syncFormHistory,
completed,
syncTabs("tabs"),
syncPasswords("passwords"),
syncBookmarks("bookmarks"),
syncHistory("history"),
syncFormHistory("forms"),
completed;
// Maintain a mapping from names ("bookmarks") to Stage enumerations (syncBookmarks).
private static final Map<String, Stage> named = new HashMap<String, Stage>();
static {
for (Stage s : EnumSet.allOf(Stage.class)) {
if (s.getRepositoryName() != null) {
named.put(s.getRepositoryName(), s);
}
}
}
public static Stage byName(final String name) {
if (name == null) {
return null;
}
return named.get(name);
}
/**
* @return an immutable collection of Stages.
*/
public static Collection<Stage> getNamedStages() {
return Collections.unmodifiableCollection(named.values());
}
// Each Stage tracks its repositoryName.
private final String repositoryName;
public String getRepositoryName() {
return repositoryName;
}
private Stage() {
this.repositoryName = null;
}
private Stage(final String name) {
this.repositoryName = name;
}
}
public void execute(GlobalSession session) throws NoSuchStageException;
public void execute() throws NoSuchStageException;
public void resetLocal();
public void wipeLocal() throws Exception;
}

View File

@ -5,6 +5,7 @@
package org.mozilla.gecko.sync.stage;
import org.mozilla.gecko.sync.CryptoRecord;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.repositories.RecordFactory;
import org.mozilla.gecko.sync.repositories.Repository;
import org.mozilla.gecko.sync.repositories.android.PasswordsRepositorySession;
@ -12,11 +13,15 @@ import org.mozilla.gecko.sync.repositories.domain.PasswordRecord;
import org.mozilla.gecko.sync.repositories.domain.Record;
public class PasswordsServerSyncStage extends ServerSyncStage {
public PasswordsServerSyncStage(GlobalSession session) {
super(session);
}
@Override
protected String getCollection() {
return "passwords";
}
@Override
protected String getEngineName() {
return "passwords";
@ -41,5 +46,4 @@ public class PasswordsServerSyncStage extends ServerSyncStage {
return r;
}
}
}

View File

@ -6,6 +6,7 @@ package org.mozilla.gecko.sync.stage;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutorService;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.sync.GlobalSession;
@ -17,12 +18,22 @@ import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SynchronizerConfiguration;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepository;
import org.mozilla.gecko.sync.repositories.InactiveSessionException;
import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
import org.mozilla.gecko.sync.repositories.RecordFactory;
import org.mozilla.gecko.sync.repositories.Repository;
import org.mozilla.gecko.sync.repositories.RepositorySession;
import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
import org.mozilla.gecko.sync.repositories.Server11Repository;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
import org.mozilla.gecko.sync.synchronizer.Synchronizer;
import org.mozilla.gecko.sync.synchronizer.SynchronizerDelegate;
import android.content.Context;
/**
* Fetch from a server collection into a local repository, encrypting
* and decrypting along the way.
@ -34,8 +45,16 @@ public abstract class ServerSyncStage implements
GlobalSyncStage,
SynchronizerDelegate {
protected GlobalSession session;
protected String LOG_TAG = "ServerSyncStage";
protected static final String LOG_TAG = "ServerSyncStage";
protected final GlobalSession session;
public ServerSyncStage(GlobalSession session) {
if (session == null) {
throw new IllegalArgumentException("session must not be null.");
}
this.session = session;
}
/**
* Override these in your subclasses.
@ -44,8 +63,11 @@ public abstract class ServerSyncStage implements
* @throws MetaGlobalException
*/
protected boolean isEnabled() throws MetaGlobalException {
return session.engineIsEnabled(this.getEngineName());
// TODO: pass EngineSettings here to check syncID and storage version.
// Catch the subclasses of MetaGlobalException to trigger various resets and wipes.
return session.engineIsEnabled(this.getEngineName(), null);
}
protected abstract String getCollection();
protected abstract String getEngineName();
protected abstract Repository getLocalRepository();
@ -77,6 +99,14 @@ public abstract class ServerSyncStage implements
return this.getCollection() + ".";
}
protected SynchronizerConfiguration getConfig() throws NonObjectJSONException, IOException, ParseException {
return new SynchronizerConfiguration(session.config.getBranch(bundlePrefix()));
}
protected void persistConfig(SynchronizerConfiguration synchronizerConfiguration) {
synchronizerConfiguration.persist(session.config.getBranch(bundlePrefix()));
}
public Synchronizer getConfiguredSynchronizer(GlobalSession session) throws NoCollectionKeysSetException, URISyntaxException, NonObjectJSONException, IOException, ParseException {
Repository remote = wrappedServerRepo();
@ -84,7 +114,7 @@ public abstract class ServerSyncStage implements
synchronizer.repositoryA = remote;
synchronizer.repositoryB = this.getLocalRepository();
SynchronizerConfiguration config = new SynchronizerConfiguration(session.config.getBranch(bundlePrefix()));
SynchronizerConfiguration config = this.getConfig();
synchronizer.load(config);
// TODO: should wipe in either direction?
@ -93,11 +123,178 @@ public abstract class ServerSyncStage implements
}
@Override
public void execute(GlobalSession session) throws NoSuchStageException {
public void resetLocal() {
// Clear both timestamps.
SynchronizerConfiguration config;
try {
config = this.getConfig();
} catch (Exception e) {
Logger.warn(LOG_TAG, "Unable to reset " + this + ": fetching config failed.", e);
return;
}
config.localBundle.setTimestamp(0L);
config.remoteBundle.setTimestamp(0L);
Logger.info(LOG_TAG, "Reset timestamps for " + this);
persistConfig(config);
}
// Not thread-safe. Use with caution.
private class WipeWaiter {
public boolean sessionSucceeded = true;
public boolean wipeSucceeded = true;
public Exception error;
public void notify(Exception e, boolean sessionSucceeded) {
this.sessionSucceeded = sessionSucceeded;
this.wipeSucceeded = false;
this.error = e;
this.notify();
}
}
/**
* Synchronously wipe this stage by instantiating a local repository session
* and wiping that.
*
* Logs and rethrows an exception on failure.
*/
@Override
public void wipeLocal() throws Exception {
// Reset, then clear data.
this.resetLocal();
final WipeWaiter monitor = new WipeWaiter();
final Context context = session.getContext();
final Repository r = this.getLocalRepository();
final Runnable doWipe = new Runnable() {
@Override
public void run() {
r.createSession(new RepositorySessionCreationDelegate() {
@Override
public void onSessionCreated(final RepositorySession session) {
try {
session.begin(new RepositorySessionBeginDelegate() {
@Override
public void onBeginSucceeded(final RepositorySession session) {
session.wipe(new RepositorySessionWipeDelegate() {
@Override
public void onWipeSucceeded() {
try {
session.finish(new RepositorySessionFinishDelegate() {
@Override
public void onFinishSucceeded(RepositorySession session,
RepositorySessionBundle bundle) {
// Hurrah.
synchronized (monitor) {
monitor.notify();
}
}
@Override
public void onFinishFailed(Exception ex) {
// Assume that no finish => no wipe.
synchronized (monitor) {
monitor.notify(ex, true);
}
}
@Override
public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) {
return this;
}
});
} catch (InactiveSessionException e) {
// Cannot happen. Call for safety.
synchronized (monitor) {
monitor.notify(e, true);
}
}
}
@Override
public void onWipeFailed(Exception ex) {
session.abort();
synchronized (monitor) {
monitor.notify(ex, true);
}
}
@Override
public RepositorySessionWipeDelegate deferredWipeDelegate(ExecutorService executor) {
return this;
}
});
}
@Override
public void onBeginFailed(Exception ex) {
session.abort();
synchronized (monitor) {
monitor.notify(ex, true);
}
}
@Override
public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) {
return this;
}
});
} catch (InvalidSessionTransitionException e) {
session.abort();
synchronized (monitor) {
monitor.notify(e, true);
}
}
}
@Override
public void onSessionCreateFailed(Exception ex) {
synchronized (monitor) {
monitor.notify(ex, false);
}
}
@Override
public RepositorySessionCreationDelegate deferredCreationDelegate() {
return this;
}
}, context);
}
};
final Thread wiping = new Thread(doWipe);
synchronized (monitor) {
wiping.start();
try {
monitor.wait();
} catch (InterruptedException e) {
Logger.error(LOG_TAG, "Wipe interrupted.");
}
}
if (!monitor.sessionSucceeded) {
Logger.error(LOG_TAG, "Failed to create session for wipe.");
throw monitor.error;
}
if (!monitor.wipeSucceeded) {
Logger.error(LOG_TAG, "Failed to wipe session.");
throw monitor.error;
}
Logger.info(LOG_TAG, "Wiping stage complete.");
}
@Override
public void execute() throws NoSuchStageException {
final String name = getEngineName();
Logger.debug(LOG_TAG, "Starting execute for " + name);
this.session = session;
try {
if (!this.isEnabled()) {
Logger.info(LOG_TAG, "Stage " + name + " disabled; skipping.");
@ -141,7 +338,7 @@ public abstract class ServerSyncStage implements
SynchronizerConfiguration synchronizerConfiguration = synchronizer.save();
if (synchronizerConfiguration != null) {
synchronizerConfiguration.persist(session.config.getBranch(bundlePrefix()));
persistConfig(synchronizerConfiguration);
} else {
Logger.warn(LOG_TAG, "Didn't get configuration from synchronizer after success");
}

View File

@ -41,16 +41,44 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
public static final int CLIENTS_TTL_REFRESH = 604800000; // 7 days
public static final int MAX_UPLOAD_FAILURE_COUNT = 5;
protected GlobalSession session;
protected final GlobalSession session;
protected final ClientRecordFactory factory = new ClientRecordFactory();
protected ClientUploadDelegate clientUploadDelegate;
protected ClientDownloadDelegate clientDownloadDelegate;
// Be sure to use this safely via getClientsDatabaseAccessor/closeDataAccessor.
protected ClientsDatabaseAccessor db;
protected volatile boolean shouldWipe;
protected volatile boolean commandsProcessedShouldUpload;
protected final AtomicInteger uploadAttemptsCount = new AtomicInteger();
public SyncClientsEngineStage(GlobalSession session) {
if (session == null) {
throw new IllegalArgumentException("session must not be null.");
}
this.session = session;
}
protected int getClientsCount() {
return getClientsDatabaseAccessor().clientsCount();
}
protected synchronized ClientsDatabaseAccessor getClientsDatabaseAccessor() {
if (db == null) {
db = new ClientsDatabaseAccessor(session.getContext());
}
return db;
}
protected synchronized void closeDataAccessor() {
if (db == null) {
return;
}
db.close();
db = null;
}
/**
* The following two delegates, ClientDownloadDelegate and ClientUploadDelegate
* are both triggered in a chain, starting when execute() calls
@ -93,11 +121,11 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
final int clientsCount;
try {
clientsCount = db.clientsCount();
clientsCount = getClientsCount();
} finally {
// Close the database to clear cached readableDatabase/writableDatabase
// after we've completed our last transaction (db.store()).
db.close();
closeDataAccessor();
}
Logger.debug(LOG_TAG, "Database contains " + clientsCount + " clients.");
@ -119,7 +147,7 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
session.abort(new HTTPFailureException(response), "Client download failed.");
} finally {
// Close the database upon failure.
db.close();
closeDataAccessor();
}
}
@ -131,7 +159,7 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
session.abort(ex, "Failure fetching client record.");
} finally {
// Close the database upon error.
db.close();
closeDataAccessor();
}
}
@ -250,10 +278,7 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
}
@Override
public void execute(GlobalSession session) throws NoSuchStageException {
this.session = session;
init();
public void execute() throws NoSuchStageException {
if (shouldDownload()) {
downloadClientRecords(); // Will kick off upload, too
} else {
@ -261,6 +286,24 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
}
}
@Override
public void resetLocal() {
// Clear timestamps and local data.
session.config.persistServerClientRecordTimestamp(0L);
session.getClientsDelegate().setClientsCount(0);
try {
getClientsDatabaseAccessor().wipe();
} finally {
closeDataAccessor();
}
}
@Override
public void wipeLocal() throws Exception {
// Nothing more to do.
this.resetLocal();
}
protected ClientRecord newLocalClientRecord(ClientsDataDelegate delegate) {
final String ourGUID = delegate.getAccountGUID();
final String ourName = delegate.getClientName();
@ -270,10 +313,6 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
return r;
}
protected void init() {
db = new ClientsDatabaseAccessor(session.getContext());
}
// TODO: Bug 726055 - More considered handling of when to sync.
protected boolean shouldDownload() {
// Ask info/collections whether a download is needed.
@ -309,7 +348,7 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
// TODO: Bug 715792 - Process commands here.
for (int i = 0; i < commands.size(); i++) {
processor.processCommand(new ExtendedJSONObject((JSONObject)commands.get(i)));
processor.processCommand(new ExtendedJSONObject((JSONObject) commands.get(i)));
}
}
@ -370,6 +409,7 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
}
protected void wipeAndStore(ClientRecord record) {
ClientsDatabaseAccessor db = getClientsDatabaseAccessor();
if (shouldWipe) {
db.wipe();
shouldWipe = false;

View File

@ -3068,7 +3068,7 @@ var FormAssistant = {
// We only want to show autocomplete suggestions for certain elements
_isAutoComplete: function _isAutoComplete(aElement) {
if (!(aElement instanceof HTMLInputElement) ||
if (!(aElement instanceof HTMLInputElement) || aElement.readOnly ||
(aElement.getAttribute("type") == "password") ||
(aElement.hasAttribute("autocomplete") &&
aElement.getAttribute("autocomplete").toLowerCase() == "off"))

File diff suppressed because one or more lines are too long

View File

@ -95,6 +95,9 @@ MOZ_INTERNAL_SIGNING_FORMAT =
endif
SDK_SUFFIX = $(PKG_SUFFIX)
SDK = $(SDK_PATH)$(PKG_BASENAME).sdk$(SDK_SUFFIX)
ifdef UNIVERSAL_BINARY
SDK = $(SDK_PATH)$(PKG_BASENAME)-$(TARGET_CPU).sdk$(SDK_SUFFIX)
endif
# JavaScript Shell packaging
ifndef LIBXUL_SDK

View File

@ -139,6 +139,12 @@
onset="this.setAttribute('timeout', val); return val;"
onget="return parseInt(this.getAttribute('timeout')) || 0;"/>
<method name="onSearchBegin">
<body><![CDATA[
this._fireEvent("searchbegin");
]]></body>
</method>
<!-- =================== nsIDOMXULMenuListElement =================== -->
<property name="editable" readonly="true"
@ -660,6 +666,9 @@
this.mFailureItems = 0;
this.mDefaultMatchFilled = false; // clear out our prefill state.
// Notify the input that the search is beginning.
this.onSearchBegin();
// tell each session to start searching...
for (var name in this.mSessions)
try {