merge mozilla-central to mozilla-inbound. r=merge a=merge

This commit is contained in:
Sebastian Hengst 2017-08-22 11:51:20 +02:00
commit e8797465ff
160 changed files with 1947 additions and 1342 deletions

View File

@ -105,7 +105,7 @@ default:: $(BUILD_BACKEND_FILES)
endif
install_manifests := \
$(addprefix dist/,branding idl include public private sdk xpi-stage) \
$(addprefix dist/,branding idl include public private xpi-stage) \
_tests \
$(NULL)
# Skip the dist/bin install manifest when using the hybrid
@ -160,22 +160,7 @@ endif
@$(TUP) $(if $(findstring s,$(filter-out --%,$(MAKEFLAGS))),,--verbose)
$(call BUILDSTATUS,TIER_FINISH tup)
# process_install_manifest needs to be invoked with --no-remove when building
# js as standalone because automated builds are building nspr separately and
# that would remove the resulting files.
# Eventually, a standalone js build would just be able to build nspr itself,
# removing the need for the former.
ifdef JS_STANDALONE
NO_REMOVE=1
endif
# For an artifact build, _tests will already be partly populated, so run
# this install manifest with NO_REMOVE set in this case.
ifdef MOZ_ARTIFACT_BUILDS
install-_tests: NO_REMOVE=1
endif
.PHONY: $(addprefix install-,$(subst /,_,$(install_manifests)))
.PHONY: $(addprefix install-,$(install_manifests))
$(addprefix install-,$(install_manifests)): install-%: $(install_manifest_depends)
ifneq (,$(filter FasterMake+RecursiveMake,$(BUILD_BACKENDS)))
@# If we're using the hybrid FasterMake/RecursiveMake backend, we want
@ -183,7 +168,7 @@ ifneq (,$(filter FasterMake+RecursiveMake,$(BUILD_BACKENDS)))
@# same directory, because that would blow up
$(if $(wildcard _build_manifests/install/$(subst /,_,$*)),$(if $(wildcard faster/install_$(subst /,_,$*)*),$(error FasterMake and RecursiveMake ends of the hybrid build system want to handle $*)))
endif
$(addprefix $(call py_action,process_install_manifest,$(if $(NO_REMOVE),--no-remove )$*) ,$(wildcard _build_manifests/install/$(subst /,_,$*)))
$(addprefix $(call py_action,process_install_manifest,--track install_$(subst /,_,$*).track $*) ,$(wildcard _build_manifests/install/$(subst /,_,$*)))
# Dummy wrapper rule to allow the faster backend to piggy back
$(addprefix install-,$(subst /,_,$(filter dist/%,$(install_manifests)))): install-dist_%: install-dist/% ;
@ -191,10 +176,9 @@ $(addprefix install-,$(subst /,_,$(filter dist/%,$(install_manifests)))): instal
.PHONY: install-tests
install-tests: install-test-files
# Force --no-remove, because $objdir/_tests is handled by multiple manifests.
.PHONY: install-test-files
install-test-files:
$(call py_action,process_install_manifest,--no-remove _tests _build_manifests/install/_test_files)
$(call py_action,process_install_manifest,--track install__test_files.track _tests _build_manifests/install/_test_files)
include $(topsrcdir)/build/moz-automation.mk
@ -204,6 +188,9 @@ ifneq ($(filter-out maybe_clobber_profiledbuild,$(MAKECMDGOALS)),)
GARBAGE_DIRS += dist _tests
endif
# Dummy rule for the cases below where we don't depend on dist/include
recurse_pre-export::
# Windows PGO builds don't perform a clean before the 2nd pass. So, we want
# to preserve content for the 2nd pass on Windows. Everywhere else, we always
# process the install manifests as part of export.
@ -214,19 +201,15 @@ ifndef NO_PROFILE_GUIDED_OPTIMIZE
ifneq ($(OS_ARCH)_$(GNU_CC), WINNT_)
recurse_pre-export:: install-manifests
binaries::
@$(MAKE) install-manifests NO_REMOVE=1 install_manifests=dist/include
@$(MAKE) install-manifests install_manifests=dist/include
endif
endif
else # !MOZ_PROFILE_USE (normal build)
recurse_pre-export:: install-manifests
binaries::
@$(MAKE) install-manifests NO_REMOVE=1 install_manifests=dist/include
@$(MAKE) install-manifests install_manifests=dist/include
endif
# For historical reasons that are unknown, $(DIST)/sdk is always blown away
# with no regard for PGO passes. This decision could probably be revisited.
recurse_pre-export:: install-dist/sdk
recurse_artifact:
$(topsrcdir)/mach --log-no-times artifact install

View File

@ -439,7 +439,6 @@ var SidebarUI = {
sidebarBroadcaster.removeAttribute("checked");
this._box.setAttribute("sidebarcommand", "");
this._box.removeAttribute("checked");
this.title = "";
this._box.hidden = this._splitter.hidden = true;
let selBrowser = gBrowser.selectedBrowser;

View File

@ -482,6 +482,13 @@ const gStoragePressureObserver = {
return;
}
const NOTIFICATION_VALUE = "storage-pressure-notification";
let notificationBox = document.getElementById("high-priority-global-notificationbox");
if (notificationBox.getNotificationWithValue(NOTIFICATION_VALUE)) {
// Do not display the 2nd notification when there is already one
return;
}
// Don't display notification twice within the given interval.
// This is because
// - not to annoy user
@ -503,7 +510,6 @@ const gStoragePressureObserver = {
let usage = subject.QueryInterface(Ci.nsISupportsPRUint64).data
let prefStrBundle = document.getElementById("bundle_preferences");
let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
let notificationBox = document.getElementById("high-priority-global-notificationbox");
buttons.push({
label: prefStrBundle.getString("spaceAlert.learnMoreButton.label"),
accessKey: prefStrBundle.getString("spaceAlert.learnMoreButton.accesskey"),
@ -552,7 +558,7 @@ const gStoragePressureObserver = {
}
notificationBox.appendNotification(
msg, "storage-pressure-notification", null, notificationBox.PRIORITY_WARNING_HIGH, buttons, null);
msg, NOTIFICATION_VALUE, null, notificationBox.PRIORITY_WARNING_HIGH, buttons, null);
}
};

View File

@ -79,3 +79,23 @@ add_task(async function() {
await SpecialPowers.pushPrefEnv({set: [["browser.preferences.useOldOrganization", false]]});
await testOverUsageThresholdNotification();
});
// Test not displaying the 2nd notification if one is already being displayed
add_task(async function() {
const TEST_NOTIFICATION_INTERVAL_MS = 0;
await SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
await SpecialPowers.pushPrefEnv({set: [["browser.storageManager.pressureNotification.minIntervalMS", TEST_NOTIFICATION_INTERVAL_MS]]});
await notifyStoragePressure();
await notifyStoragePressure();
let notificationbox = document.getElementById("high-priority-global-notificationbox");
let allNotifications = notificationbox.allNotifications;
let pressureNotificationCount = 0;
allNotifications.forEach(notification => {
if (notification.getAttribute("value") == "storage-pressure-notification") {
pressureNotificationCount++;
}
});
is(pressureNotificationCount, 1, "Should not display the 2nd notification when there is already one");
notificationbox.removeAllNotifications();
});

View File

@ -553,7 +553,7 @@
<hbox pack="end">
<button id="locationSettingsButton"
class="accessory-button"
label="&permissionSettingsButton2.label;"
label="&locationSettingsButton.label;"
accesskey="&locationSettingsButton.accesskey;"
searchkeywords="&removepermission2.label;
&removeallpermissions2.label;
@ -571,7 +571,7 @@
<hbox pack="end">
<button id="cameraSettingsButton"
class="accessory-button"
label="&permissionSettingsButton2.label;"
label="&cameraSettingsButton.label;"
accesskey="&cameraSettingsButton.accesskey;"
searchkeywords="&removepermission2.label;
&removeallpermissions2.label;
@ -589,7 +589,7 @@
<hbox pack="end">
<button id="microphoneSettingsButton"
class="accessory-button"
label="&permissionSettingsButton2.label;"
label="&microphoneSettingsButton.label;"
accesskey="&microphoneSettingsButton.accesskey;"
searchkeywords="&removepermission2.label;
&removeallpermissions2.label;
@ -609,7 +609,7 @@
<hbox pack="end">
<button id="notificationSettingsButton"
class="accessory-button"
label="&permissionSettingsButton2.label;"
label="&notificationSettingsButton.label;"
accesskey="&notificationSettingsButton.accesskey;"
searchkeywords="&removepermission2.label;
&removeallpermissions2.label;

View File

@ -106,7 +106,7 @@ var gSyncPane = {
// Use cached values while we wait for the up-to-date values
let cachedComputerName = Services.prefs.getCharPref("services.sync.client.name", "");
document.getElementById("fxaEmailAddress1").textContent = username;
document.querySelector(".fxaEmailAddress").value = username;
this._populateComputerName(cachedComputerName);
this.page = FXA_PAGE_LOGGED_IN;
},
@ -255,8 +255,7 @@ var gSyncPane = {
.wrappedJSObject;
let displayNameLabel = document.getElementById("fxaDisplayName");
let fxaEmailAddress1Label = document.getElementById("fxaEmailAddress1");
fxaEmailAddress1Label.hidden = false;
let fxaEmailAddressLabels = document.querySelectorAll(".fxaEmailAddress");
displayNameLabel.hidden = true;
// determine the fxa status...
@ -291,9 +290,9 @@ var gSyncPane = {
fxaLoginStatus.selectedIndex = FXA_LOGIN_VERIFIED;
syncReady = true;
}
fxaEmailAddress1Label.textContent = data.email;
document.getElementById("fxaEmailAddress2").textContent = data.email;
document.getElementById("fxaEmailAddress3").textContent = data.email;
fxaEmailAddressLabels.forEach((label) => {
label.value = data.email;
});
this._populateComputerName(Weave.Service.clientsEngine.localName);
let engines = document.getElementById("fxaSyncEngines")
for (let checkbox of engines.querySelectorAll("checkbox")) {
@ -318,9 +317,9 @@ var gSyncPane = {
if (data.email) {
// A hack to handle that the user's email address may have changed.
// This can probably be removed as part of bug 1383663.
fxaEmailAddress1Label.textContent = data.email;
document.getElementById("fxaEmailAddress2").textContent = data.email;
document.getElementById("fxaEmailAddress3").textContent = data.email;
fxaEmailAddressLabels.forEach((label) => {
label.value = data.email;
});
}
if (data.displayName) {
fxaLoginStatus.setAttribute("hasName", true);

View File

@ -102,18 +102,16 @@
onkeypress="gSyncPane.openChangeProfileImage(event);"
tooltiptext="&profilePicture.tooltip;"/>
<vbox flex="1" pack="center">
<hbox flex="1">
<hbox flex="1" align="center">
<label id="fxaDisplayName" hidden="true"/>
<label id="fxaEmailAddress1"/>
</hbox>
<hbox flex="1" align="center">
<label id="fxaDisplayName" hidden="true"/>
<label class="fxaEmailAddress" flex="1" crop="end"/>
<button id="fxaUnlinkButton"
class="accessory-button"
label="&disconnect3.label;"
accesskey="&disconnect3.accesskey;"/>
</hbox>
<hbox>
<html:a id="verifiedManage" class="openLink" target="_blank"
<html:a id="verifiedManage" class="openLink"
accesskey="&verifiedManage.accesskey;"
onkeypress="gSyncPane.openManageFirefoxAccount(event);">&verifiedManage.label;</html:a>
</hbox>
@ -125,12 +123,12 @@
<vbox>
<image class="fxaProfileImage"/>
</vbox>
<vbox flex="1">
<vbox flex="1" pack="center">
<hbox>
<vbox><image class="fxaLoginRejectedWarning"/></vbox>
<image class="fxaLoginRejectedWarning"/>
<description flex="1">
&signedInUnverified.beforename.label;
<label id="fxaEmailAddress2"/>
<label class="fxaEmailAddress"/>
&signedInUnverified.aftername.label;
</description>
</hbox>
@ -146,12 +144,12 @@
<vbox>
<image class="fxaProfileImage"/>
</vbox>
<vbox flex="1">
<vbox flex="1" pack="center">
<hbox>
<vbox><image class="fxaLoginRejectedWarning"/></vbox>
<image class="fxaLoginRejectedWarning"/>
<description flex="1">
&signedInLoginFailure.beforename.label;
<label id="fxaEmailAddress3"/>
<label class="fxaEmailAddress"/>
&signedInLoginFailure.aftername.label;
</description>
</hbox>

View File

@ -13,6 +13,9 @@ fi
if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
ac_add_options --with-macbundlename-prefix=Firefox
fi
if test "${MOZ_UPDATE_CHANNEL}" = "nightly-try"; then
ac_add_options --with-macbundlename-prefix=Firefox
fi
ac_add_options --with-branding=browser/branding/nightly

View File

@ -17,6 +17,7 @@ if test "$OS_ARCH" = "WINNT"; then
MOZ_MAINTENANCE_SERVICE=1
if ! test "$HAVE_64BIT_BUILD"; then
if test "$MOZ_UPDATE_CHANNEL" = "nightly" -o \
"$MOZ_UPDATE_CHANNEL" = "nightly-try" -o \
"$MOZ_UPDATE_CHANNEL" = "aurora" -o \
"$MOZ_UPDATE_CHANNEL" = "aurora-dev" -o \
"$MOZ_UPDATE_CHANNEL" = "beta" -o \

View File

@ -586,11 +586,10 @@ dataReportingNotification.button.accessKey = C
# Process hang reporter
processHang.label = A web page is slowing down your browser. What would you like to do?
# LOCALIZATION NOTE (processHang.add-on.label): The first %S is the name of
# an extension. The second %S is the name of the product (e.g., Firefox)
processHang.add-on.label = A script in the extension “%S” is causing %S to slow down.
# LOCALIZATION NOTE (processHang.add-on.label): %1$S is the name of the
# extension. %2$S is the name of the product (e.g., Firefox)
processHang.add-on.label = A script in the extension “%1$S” is causing %2$S to slow down.
processHang.add-on.learn-more.text = Learn more
processHang.add-on.learn-more.url = https://support.mozilla.org/en-US/kb/warning-unresponsive-script?cache=no#w_other-causes
processHang.button_stop.label = Stop It
processHang.button_stop.accessKey = S
processHang.button_stop_sandbox.label = Temporarily Disable Extension on Page

View File

@ -14,20 +14,22 @@
<!ENTITY popupExceptions.label "Exceptions…">
<!ENTITY popupExceptions.accesskey "E">
<!ENTITY permissionSettingsButton2.label "Settings…">
<!ENTITY notificationPermissions.label "Notifications">
<!ENTITY notificationSettingsButton.accesskey "n">
<!ENTITY notificationSettingsButton.label "Settings…">
<!ENTITY notificationSettingsButton.accesskey "t">
<!ENTITY notificationPermissionsLearnMore.label "Learn more">
<!ENTITY locationPermissions.label "Location">
<!ENTITY locationSettingsButton.accesskey "l">
<!ENTITY locationSettingsButton.label "Settings…">
<!ENTITY locationSettingsButton.accesskey "t">
<!ENTITY cameraPermissions.label "Camera">
<!ENTITY cameraSettingsButton.accesskey "c">
<!ENTITY cameraSettingsButton.label "Settings…">
<!ENTITY cameraSettingsButton.accesskey "t">
<!ENTITY microphonePermissions.label "Microphone">
<!ENTITY microphoneSettingsButton.accesskey "m">
<!ENTITY microphoneSettingsButton.label "Settings…">
<!ENTITY microphoneSettingsButton.accesskey "t">
<!ENTITY fontsAndColors.label "Fonts &amp; Colors">

View File

@ -5,7 +5,7 @@
<!-- Localization note (reportDeceptiveSiteMenu.title) - Label of the Help menu
item. Either this or safeb.palm.notdeceptive.label from
phishing-afterload-warning-message.dtd is shown. -->
<!ENTITY reportDeceptiveSiteMenu.title "Report deceptive site…">
<!ENTITY reportDeceptiveSiteMenu.title "Report Deceptive Site…">
<!-- Localization note (reportDeceptiveSiteMenu.accesskey) - Because
safeb.palm.notdeceptive.label from phishing-afterload-warning-message.dtd and
reportDeceptiveSiteMenu.title are never shown at the same time, the same

View File

@ -342,7 +342,7 @@ var ProcessHangMonitor = {
[addonName, brandBundle.getString("brandShortName")]);
let linkText = bundle.getString("processHang.add-on.learn-more.text");
let linkURL = bundle.getString("processHang.add-on.learn-more.url");
let linkURL = "https://support.mozilla.org/kb/warning-unresponsive-script#w_other-causes";
let link = doc.createElement("label");
link.setAttribute("class", "text-link");

View File

@ -578,23 +578,6 @@ html|span.ac-emphasize-text-url {
list-style-image: url("chrome://browser/skin/Info.png");
}
/* Reader mode button */
#reader-mode-button {
list-style-image: url("chrome://browser/skin/readerMode.svg");
-moz-image-region: rect(0, 16px, 16px, 0);
}
#reader-mode-button:hover,
#reader-mode-button[readeractive]:hover {
-moz-image-region: rect(0, 32px, 16px, 16px);
}
#reader-mode-button:hover:active,
#reader-mode-button[readeractive] {
-moz-image-region: rect(0, 48px, 16px, 32px);
}
/* Bookmarking panel */
#editBookmarkPanelStarIcon {
list-style-image: url("chrome://browser/skin/places/starred48.png");
@ -624,8 +607,9 @@ html|span.ac-emphasize-text-url {
%include ../shared/sidebar.inc.css
#sidebar {
background-color: Window;
#sidebar-box {
background-color: -moz-Field;
color: -moz-FieldText;
}
#sidebar-header {

View File

@ -4,6 +4,11 @@
/* Sidebars */
:root {
-moz-appearance: none;
background-color: transparent;
}
#sidebar-search-container {
padding: 4px;
}

View File

@ -587,21 +587,6 @@ html|span.ac-emphasize-text-url {
}
}
/* Reader mode button */
#reader-mode-button {
list-style-image: url("chrome://browser/skin/readerMode.svg");
-moz-image-region: rect(0, 16px, 16px, 0);
}
#reader-mode-button:hover:active {
-moz-image-region: rect(0, 32px, 16px, 16px);
}
#reader-mode-button[readeractive] {
-moz-image-region: rect(0, 48px, 16px, 32px);
}
/* BOOKMARKING PANEL */
#editBookmarkPanelStarIcon {
list-style-image: url("chrome://browser/skin/places/starred48.png");

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
class="fieldtext"
width="16" height="16" viewBox="0 0 16 16">
#include ../icon-colors.inc.svg
<defs>
<path id="shape-notifications-addons" d="M10,15c0.5,0,1-0.4,1-1v-3c0,0,0-0.8,0.8-0.8c0.6,0,0.6,0.8,1.8,0.8c0.6,0,1.5-0.2,1.5-2c0-1.8-0.9-2-1.5-2 c-1.1,0-1.1,0.7-1.8,0.7C11,7.7,11,7,11,7V6c0-0.6-0.5-1-1-1H8c0,0-0.8,0-0.8-0.8C7.2,3.6,8,3.6,8,2.5C8,1.9,7.8,1,6,1 C4.2,1,4,1.9,4,2.5c0,1.1,0.8,1.1,0.8,1.8C4.8,5,4,5,4,5H2C1.5,5,1,5.4,1,6l0,1.5c0,0-0.1,1,1.1,1c0.8,0,0.9-1,1.9-1 C4.5,7.4,5,8,5,9c0,1-0.5,1.6-1,1.6c-1,0-1.1-1.1-1.9-1.1C0.9,9.5,1,10.8,1,10.8V14c0,0.6,0.5,1,1,1l2.6,0c0,0,1.1,0,1.1-1 c0-0.8-1-0.1-1-1.1c0-0.5,0.7-1.2,1.8-1.2s1.8,0.7,1.8,1.2c0,1-1.1,0.3-1.1,1.1c0,1,1.2,1,1.2,1H10z"/>
</defs>
<use xlink:href="#shape-notifications-addons"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -2,5 +2,5 @@
- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="context-fill" d="M14.3 15h-1.5a.789.789 0 0 1-.8-.8s.3-3.6-3.2-7.2c-2.5-3-7-3.2-7-3.2a.713.713 0 0 1-.8-.7V1.7a.713.713 0 0 1 .8-.7s6.3.4 9.6 4.5c3.3 3.1 3.6 8.8 3.6 8.8a.632.632 0 0 1-.7.7zM1.8 6s3.7.5 5.8 2.4c2.1 2 2.5 5.9 2.5 5.9 0 .4-.1.8-.5.8H8.1c-.4 0-.6-.3-.6-.8a5.929 5.929 0 0 0-1.8-4.2 7.256 7.256 0 0 0-3.9-1.4A.713.713 0 0 1 1 8V6.7a.713.713 0 0 1 .8-.7zM3 11a2 2 0 1 1-2 2 2.006 2.006 0 0 1 2-2z"/>
<path fill="context-fill" d="M3.5 10A2.5 2.5 0 1 0 6 12.5 2.5 2.5 0 0 0 3.5 10zM2 1a1 1 0 0 0 0 2 10.883 10.883 0 0 1 11 11 1 1 0 0 0 2 0A12.862 12.862 0 0 0 2 1zm0 4a1 1 0 0 0 0 2 6.926 6.926 0 0 1 7 7 1 1 0 0 0 2 0 8.9 8.9 0 0 0-9-9z"/>
</svg>

Before

Width:  |  Height:  |  Size: 729 B

After

Width:  |  Height:  |  Size: 544 B

View File

@ -2,5 +2,5 @@
- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="context-fill" d="M9.5 1A5.549 5.549 0 0 0 4 6.5a5.291 5.291 0 0 0 .9 3L1.3 13a1.217 1.217 0 0 0 0 1.6 1.2 1.2 0 0 0 .8.4 1.33 1.33 0 0 0 .8-.3l3.5-3.5a5.291 5.291 0 0 0 3 .9A5.551 5.551 0 0 0 9.5 1zm0 9A3.543 3.543 0 0 1 6 6.5a3.5 3.5 0 0 1 7 0A3.543 3.543 0 0 1 9.5 10z"/>
<path fill="context-fill" d="M15.707 14.293l-4.822-4.822a6.019 6.019 0 1 0-1.414 1.414l4.822 4.822a1 1 0 0 0 1.414-1.414zM6 10a4 4 0 1 1 4-4 4 4 0 0 1-4 4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 464 B

View File

@ -2,5 +2,5 @@
- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="context-fill" d="M12 11V5l3 3zm-7 1h6l-3 3zm5-1H6a.945.945 0 0 1-1-1V6a.945.945 0 0 1 1-1h4a.945.945 0 0 1 1 1v4a1 1 0 0 1-1 1zm0-3a.945.945 0 0 0-1-1H7a.945.945 0 0 0-1 1v1a.945.945 0 0 0 1 1h2a.945.945 0 0 0 1-1zM8 1l3 3H5zM4 5v6L1 8z"/>
<path fill="context-fill" d="M7.707 8.293a1 1 0 0 0-1.414 0L3 11.586V9a1 1 0 0 0-2 0v5a1 1 0 0 0 1 1h5a1 1 0 1 0 0-2H4.414l3.293-3.293a1 1 0 0 0 0-1.414zM14 1H9a1 1 0 0 0 0 2h2.586L8.293 6.293a1 1 0 1 0 1.414 1.414L13 4.414V7a1 1 0 0 0 2 0V2a1 1 0 0 0-1-1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 557 B

After

Width:  |  Height:  |  Size: 565 B

View File

@ -2,5 +2,5 @@
- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="context-fill" d="M11.4 13.7l-.6-1.5c.3-.2.5-.4.8-.6.2-.2.4-.5.6-.7l1.5.6c.3.1.5 0 .7-.3l.4-.9c.1-.3 0-.5-.3-.7L12.9 9a6.054 6.054 0 0 0 0-1.9l1.5-.6a.517.517 0 0 0 .3-.7l-.4-.9a.517.517 0 0 0-.7-.3l-1.5.6a1.874 1.874 0 0 0-.6-.7c-.2-.2-.5-.4-.7-.6l.6-1.5c.1-.3 0-.5-.3-.7l-.9-.4c-.3-.1-.5 0-.7.3L9 3.1a6.054 6.054 0 0 0-1.9 0l-.6-1.5a.517.517 0 0 0-.7-.3l-.9.4a.61.61 0 0 0-.3.7l.6 1.5a1.874 1.874 0 0 0-.7.6 4.349 4.349 0 0 0-.6.7l-1.5-.7c-.3-.1-.5 0-.7.3l-.3.9a.574.574 0 0 0 .2.7l1.5.6a6.054 6.054 0 0 0 0 1.9l-1.5.6a.517.517 0 0 0-.3.7l.4.9a.517.517 0 0 0 .7.3l1.5-.6a1.874 1.874 0 0 0 .6.7c.2.2.5.4.7.6l-.6 1.5c-.1.3 0 .5.3.7l.9.4c.3.1.5 0 .7-.3l.5-1.5a6.054 6.054 0 0 0 1.9 0l.6 1.5a.517.517 0 0 0 .7.3l.9-.4c.2-.1.4-.4.3-.6zm-5-4.1a2.263 2.263 0 1 1 3.2-3.2 2.263 2.263 0 0 1-3.2 3.2z"/>
<path fill="context-fill" d="M15 7h-2.1a4.967 4.967 0 0 0-.732-1.753l1.49-1.49a1 1 0 0 0-1.414-1.414l-1.49 1.49A4.968 4.968 0 0 0 9 3.1V1a1 1 0 0 0-2 0v2.1a4.968 4.968 0 0 0-1.753.732l-1.49-1.49a1 1 0 0 0-1.414 1.415l1.49 1.49A4.967 4.967 0 0 0 3.1 7H1a1 1 0 0 0 0 2h2.1a4.968 4.968 0 0 0 .737 1.763c-.014.013-.032.017-.045.03l-1.45 1.45a1 1 0 1 0 1.414 1.414l1.45-1.45c.013-.013.018-.031.03-.045A4.968 4.968 0 0 0 7 12.9V15a1 1 0 0 0 2 0v-2.1a4.968 4.968 0 0 0 1.753-.732l1.49 1.49a1 1 0 0 0 1.414-1.414l-1.49-1.49A4.967 4.967 0 0 0 12.9 9H15a1 1 0 0 0 0-2zM5 8a3 3 0 1 1 3 3 3 3 0 0 1-3-3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 900 B

View File

@ -2,5 +2,5 @@
- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="context-fill" d="M7.707 8.293a1 1 0 0 0-1.414 0L3 11.586V9a1 1 0 0 0-2 0v5a1 1 0 0 0 1 1h5a1 1 0 1 0 0-2H4.414l3.293-3.293a1 1 0 0 0 0-1.414zM14 1H9a1 1 0 0 0 0 2h2.586L8.293 6.293a1 1 0 1 0 1.414 1.414L13 4.414V7a1 1 0 0 0 2 0V2a1 1 0 0 0-1-1z"/>
<path fill="context-fill" d="M13 1H3a3 3 0 0 0-3 3v8a3 3 0 0 0 3 3h11a2 2 0 0 0 2-2V4a3 3 0 0 0-3-3zm1 11a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V6h12zm0-7H2V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 565 B

After

Width:  |  Height:  |  Size: 490 B

View File

@ -1,6 +1,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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="context-fill" d="M13 9H9v4H7V9H3V7h4V3h2v4h4z"/>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="context-fill">
<path d="M14 7H9V2a1 1 0 0 0-2 0v5H2a1 1 0 0 0 0 2h5v5a1 1 0 0 0 2 0V9h5a1 1 0 0 0 0-2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 397 B

View File

@ -1,6 +1,6 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="context-fill" d="M3 7h10v2H3z"/>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="context-fill">
<rect x="2" y="7" width="12" height="2" rx="1"/>
</svg>

Before

Width:  |  Height:  |  Size: 350 B

After

Width:  |  Height:  |  Size: 354 B

View File

@ -110,7 +110,7 @@
}
#urlbar[actiontype="extension"] > #identity-box > #identity-icon {
list-style-image: url(chrome://browser/skin/addons/addon-install-anchor.svg);
list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.svg);
}
/* SHARING ICON */

View File

@ -566,20 +566,19 @@ separator.thin:not([orient="vertical"]) {
color: var(--in-content-link-color);
}
#fxaLoginStatus[hasName] #fxaEmailAddress1 {
font-size: 1.1rem;
#fxaDisplayName {
font-weight: bold;
margin-inline-end: 10px !important;
}
#fxaEmailAddress1,
#fxaEmailAddress2,
#fxaEmailAddress3 {
word-break: break-all;
.fxaEmailAddress {
margin-inline-end: 8px !important;
}
.fxaLoginRejectedWarning {
list-style-image: url(chrome://browser/skin/warning.svg);
filter: drop-shadow(0 1px 0 hsla(206, 50%, 10%, .15));
margin: 4px 8px 0px 0px;
margin-inline-start: 4px;
margin-inline-end: 8px;
}
#fxaSyncEngines > vbox > checkbox {

View File

@ -20,7 +20,6 @@
skin/classic/browser/addons/addon-install-installed.svg (../shared/addons/addon-install-installed.svg)
skin/classic/browser/addons/addon-install-restart.svg (../shared/addons/addon-install-restart.svg)
skin/classic/browser/addons/addon-install-warning.svg (../shared/addons/addon-install-warning.svg)
* skin/classic/browser/addons/addon-install-anchor.svg (../shared/addons/addon-install-anchor.svg)
* skin/classic/browser/controlcenter/conn-not-secure.svg (../shared/controlcenter/conn-not-secure.svg)
* skin/classic/browser/controlcenter/connection.svg (../shared/controlcenter/connection.svg)
* skin/classic/browser/controlcenter/mcb-disabled.svg (../shared/controlcenter/mcb-disabled.svg)
@ -147,7 +146,6 @@
skin/classic/browser/forget.svg (../shared/icons/forget.svg)
skin/classic/browser/forward.svg (../shared/icons/forward.svg)
skin/classic/browser/fullscreen.svg (../shared/icons/fullscreen.svg)
skin/classic/browser/fullscreen-enter.svg (../shared/icons/fullscreen-enter.svg)
skin/classic/browser/fullscreen-exit.svg (../shared/icons/fullscreen-exit.svg)
skin/classic/browser/history.svg (../shared/icons/history.svg)
skin/classic/browser/home.svg (../shared/icons/home.svg)
@ -175,6 +173,7 @@
skin/classic/browser/synced-tabs.svg (../shared/icons/synced-tabs.svg)
skin/classic/browser/toolbar.svg (../shared/icons/toolbar.svg)
skin/classic/browser/webIDE.svg (../shared/icons/webIDE.svg)
skin/classic/browser/window.svg (../shared/icons/window.svg)
skin/classic/browser/zoom-in.svg (../shared/icons/zoom-in.svg)
skin/classic/browser/zoom-out.svg (../shared/icons/zoom-out.svg)

View File

@ -1,6 +1,5 @@
/* Menu panel and palette styles */
#appMenuRecentlyClosedWindows,
#appMenu-new-window-button {
list-style-image: url(chrome://browser/skin/new-window.svg);
}
@ -63,7 +62,7 @@
}
#appMenu-fullscreen-button {
list-style-image: url(chrome://browser/skin/fullscreen-enter.svg);
list-style-image: url(chrome://browser/skin/fullscreen.svg);
}
#appMenu-fullscreen-button[checked] {
@ -123,3 +122,8 @@ toolbarpaletteitem[place="palette"] > #bookmarks-menu-button,
#appMenuRestoreLastSession {
list-style-image: url("chrome://browser/skin/reload.svg");
}
#appMenuRecentlyClosedWindows {
list-style-image: url(chrome://browser/skin/window.svg);
}

View File

@ -175,7 +175,7 @@ html|*#webRTC-previewVideo {
/* INSTALL ADDONS */
.install-icon {
list-style-image: url(chrome://browser/skin/addons/addon-install-anchor.svg);
list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.svg);
}
.popup-notification-icon[popupid="xpinstall-disabled"],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,29 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="16" viewBox="0 0 48 16">
<defs>
<path id="glyphShape-readerMode-book" d="M5.5,5h-2C3.2,5,3,5.2,3,5.5S3.2,6,3.5,6h2 C5.8,6,6,5.8,6,5.5S5.8,5,5.5,5z M5.5,7h-2C3.2,7,3,7.2,3,7.5S3.2,8,3.5,8h2C5.8,8,6,7.8,6,7.5S5.8,7,5.5,7z M5.5,9h-2 C3.2,9,3,9.2,3,9.5S3.2,10,3.5,10h2C5.8,10,6,9.8,6,9.5S5.8,9,5.5,9z M15.4,2c0,0-3.1,0-4.4,0S8.1,2.5,8,4.3C7.9,2.5,6.3,2,5,2 S0.6,2,0.6,2C0.3,2,0,2.3,0,2.7v9.6C0,12.6,0.3,13,0.6,13c0,0,2.6,0,4.4,0c1.6,0,2.8,1,3,2.3C8.2,14,9.4,13,11,13 c1.8,0,4.4,0,4.4,0c0.4,0,0.6-0.4,0.6-0.8V2.7C16,2.3,15.7,2,15.4,2z M14,11L14,11c-0.2,0-1.6,0-3,0c-1.6,0-2.9,0.8-3,2.2 C7.9,11.8,6.6,11,5,11c-1.4,0-2.8,0-3,0l0,0l0,0V4c0,0,2.7,0,3.5,0C6.6,4,8,5.5,8,6.8C8,5.5,9.4,4,10.5,4C11.3,4,14,4,14,4V11 L14,11z"/>
<linearGradient id="gradient-state-default" x1="0%" y1="0%" x2="0" y2="100%">
<stop stop-color="#989898" offset="0%"/>
<stop stop-color="#808080" offset="100%"/>
</linearGradient>
<linearGradient id="gradient-state-hover" x1="0%" y1="0%" x2="0" y2="100%">
<stop stop-color="#24aef4" offset="0%"/>
<stop stop-color="#177bdb" offset="100%"/>
</linearGradient>
<linearGradient id="gradient-state-pressed" x1="0%" y1="0%" x2="0" y2="100%">
<stop stop-color="#ff9300" offset="0%"/>
<stop stop-color="#ff5500" offset="100%"/>
</linearGradient>
<style>
.icon-state-default { fill: url(#gradient-state-default); }
.icon-state-hover { fill: url(#gradient-state-hover); }
.icon-state-pressed { fill: url(#gradient-state-pressed); }
</style>
</defs>
<use xlink:href="#glyphShape-readerMode-book" class="icon-state-default"/>
<use xlink:href="#glyphShape-readerMode-book" class="icon-state-hover" transform="translate(16)"/>
<use xlink:href="#glyphShape-readerMode-book" class="icon-state-pressed" transform="translate(32)"/>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M13 0H3a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm0 13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1zm-2.5-9h-5a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1zm0 2h-5a.5.5 0 0 0 0 1h5a.5.5 0 1 0 0-1zm0 2h-5a.5.5 0 0 0 0 1h5a.5.5 0 1 0 0-1zm-3 2h-2a.5.5 0 0 0 0 1h2a.5.5 0 1 0 0-1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 664 B

View File

@ -345,9 +345,11 @@
background-clip: padding-box;
}
%ifdef MENUBAR_CAN_AUTOHIDE
#toolbar-menubar:not([autohide=true]) ~ #TabsToolbar > #tabbrowser-tabs > .tabbrowser-tab > .tab-stack > .tab-background {
border-top-style: solid;
}
%endif
.tab-background[selected=true] {
border-left-style: solid;

View File

@ -125,6 +125,8 @@
opacity: 0;
}
/* Page actions */
#page-action-buttons {
-moz-box-align: center;
/* Add more space between the last icon and the urlbar's edge. */
@ -220,7 +222,19 @@
animation-name: bookmark-animation-rtl;
}
/* Reader mode icon */
#reader-mode-button {
list-style-image: url(chrome://browser/skin/readerMode.svg);
}
#reader-mode-button[readeractive] {
fill: var(--toolbarbutton-icon-fill-attention);
fill-opacity: 1;
}
/* Zoom button */
#urlbar-zoom-button {
margin: 0 3px;
font-size: .8em;

View File

@ -16,12 +16,6 @@
}
@media (-moz-windows-default-theme) {
.sidebar-header,
#sidebar-header {
-moz-appearance: none;
border-bottom: none;
}
.menu-accel,
.menu-iconic-accel {
color: graytext;

View File

@ -766,23 +766,6 @@ treechildren.searchbar-treebody::-moz-tree-row(selected) {
-moz-image-region: rect(0, 48px, 16px, 32px);
}
/* Reader mode button */
#reader-mode-button {
list-style-image: url("chrome://browser/skin/readerMode.svg");
-moz-image-region: rect(0, 16px, 16px, 0);
}
#reader-mode-button:hover,
#reader-mode-button[readeractive]:hover {
-moz-image-region: rect(0, 32px, 16px, 16px);
}
#reader-mode-button:hover:active,
#reader-mode-button[readeractive] {
-moz-image-region: rect(0, 48px, 16px, 32px);
}
/* bookmarking panel */
#editBookmarkPanelStarIcon {
@ -813,8 +796,9 @@ treechildren.searchbar-treebody::-moz-tree-row(selected) {
%include ../shared/sidebar.inc.css
#sidebar {
background-color: Window;
#sidebar-box {
background-color: -moz-Field;
color: -moz-FieldText;
}
#sidebar-header {

View File

@ -3,11 +3,24 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Sidebars */
:root {
background-color: transparent;
}
#sidebar-search-container {
padding: 4px;
}
.sidebar-placesTree {
-moz-appearance: none;
border: 0;
margin: 0;
border-top: 1px solid ThreeDShadow;
}
.sidebar-placesTreechildren::-moz-tree-cell,
.sidebar-placesTreechildren::-moz-tree-twisty {
padding: 0 4px;
}
.sidebar-placesTreechildren::-moz-tree-cell(leaf) ,

View File

@ -3693,7 +3693,7 @@ MediaElementTableCount(HTMLMediaElement* aElement, nsIURI* aURI)
void
HTMLMediaElement::AddMediaElementToURITable()
{
NS_ASSERTION(mDecoder && mDecoder->GetResource(), "Call this only with decoder Load called");
NS_ASSERTION(mDecoder, "Call this only with decoder Load called");
NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
"Should not have entry for element in element table before addition");
if (!gElementTable) {

View File

@ -8,9 +8,10 @@ KillScriptWithDebugMessage=A script on this page may be busy, or it may have sto
KillScriptLocation=Script: %S
KillAddonScriptTitle=Warning: Unresponsive add-on script
# LOCALIZATION NOTE (KillAddonScriptMessage): The first %S is the name of an add-on. The second %S is the name of the application (e.g., Firefox).
KillAddonScriptMessage=A script from the add-on “%S” is running on this page, and making %S unresponsive.\n\nIt may be busy, or it may have stopped responsing permanently. You can stop the script now, or you can continue to see if it will complete.
KillAddonScriptGlobalMessage=Prevent the add-on script from running on this page until it next reloads.
# LOCALIZATION NOTE (KillAddonScriptMessage): %1$S is the name of an extension.
# %2$S is the name of the application (e.g., Firefox).
KillAddonScriptMessage=A script from the extension “%1$S” is running on this page, and making %2$S unresponsive.\n\nIt may be busy, or it may have stopped responding permanently. You can stop the script now, or you can continue to see if it will complete.
KillAddonScriptGlobalMessage=Prevent the extension script from running on this page until it next reloads
StopScriptButton=Stop script
DebugScriptButton=Debug script

View File

@ -350,6 +350,13 @@ ChannelMediaDecoder::CanPlayThroughImpl()
return GetStatistics().CanPlayThrough();
}
bool
ChannelMediaDecoder::IsLiveStream()
{
MOZ_ASSERT(NS_IsMainThread());
return mResource->IsLiveStream();
}
void
ChannelMediaDecoder::OnPlaybackEvent(MediaEventType aEvent)
{

View File

@ -67,8 +67,6 @@ protected:
public:
explicit ChannelMediaDecoder(MediaDecoderInit& aInit);
MediaResource* GetResource() const override final;
void Shutdown() override;
bool CanClone();
@ -85,6 +83,8 @@ public:
void Resume() override;
private:
MediaResource* GetResource() const override final;
// Create a new state machine to run this decoder.
MediaDecoderStateMachine* CreateStateMachine();
@ -104,6 +104,8 @@ private:
bool CanPlayThroughImpl() override final;
bool IsLiveStream() override final;
// The actual playback rate computation.
void ComputePlaybackRate();

View File

@ -565,7 +565,6 @@ void
MediaDecoder::FinishShutdown()
{
MOZ_ASSERT(NS_IsMainThread());
mDecoderStateMachine->BreakCycles();
SetStateMachine(nullptr);
mVideoFrameContainer = nullptr;
MediaShutdownManager::Instance().Unregister(this);
@ -682,6 +681,11 @@ MediaDecoder::CallSeek(const SeekTarget& aTarget)
AbstractThread::AutoEnter context(AbstractMainThread());
DiscardOngoingSeekIfExists();
// Since we don't have a listener for changes in IsLiveStream, our best bet
// is to ensure IsLiveStream is uptodate when seek begins. This value will be
// checked when seek is completed.
mDecoderStateMachine->DispatchIsLiveStream(IsLiveStream());
mDecoderStateMachine->InvokeSeek(aTarget)
->Then(mAbstractMainThread, __func__, this,
&MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected)
@ -804,9 +808,9 @@ MediaDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
AbstractThread::AutoEnter context(AbstractMainThread());
LOG("FirstFrameLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d "
"mPlayState=%s",
"mPlayState=%s transportSeekable=%d",
aInfo->mAudio.mChannels, aInfo->mAudio.mRate, aInfo->HasAudio(),
aInfo->HasVideo(), PlayStateStr());
aInfo->HasVideo(), PlayStateStr(), GetResource()->IsTransportSeekable());
mInfo = aInfo.forget();

View File

@ -120,15 +120,6 @@ public:
// Called if the media file encounters a network error.
void NetworkError();
// Get the current MediaResource being used.
// Note: The MediaResource is refcounted, but it outlives the MediaDecoder,
// so it's OK to use the reference returned by this function without
// refcounting, *unless* you need to store and use the reference after the
// MediaDecoder has been destroyed. You might need to do this if you're
// wrapping the MediaResource in some kind of byte stream interface to be
// passed to a platform decoder.
virtual MediaResource* GetResource() const = 0;
// Return the principal of the current URI being played or downloaded.
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
@ -490,6 +481,15 @@ protected:
media::TimeUnit::FromMicroseconds(250000);
private:
// Get the current MediaResource being used.
// Note: The MediaResource is refcounted, but it outlives the MediaDecoder,
// so it's OK to use the reference returned by this function without
// refcounting, *unless* you need to store and use the reference after the
// MediaDecoder has been destroyed. You might need to do this if you're
// wrapping the MediaResource in some kind of byte stream interface to be
// passed to a platform decoder.
virtual MediaResource* GetResource() const = 0;
nsCString GetDebugInfo();
// Called when the owner's activity changed.
@ -510,6 +510,7 @@ private:
void DisconnectMirrors();
virtual bool CanPlayThroughImpl() = 0;
virtual bool IsLiveStream() = 0;
// The state machine object for handling the decoding. It is safe to
// call methods of this object from other threads. Its internal data

View File

@ -257,7 +257,6 @@ protected:
using Master = MediaDecoderStateMachine;
explicit StateObject(Master* aPtr) : mMaster(aPtr) { }
TaskQueue* OwnerThread() const { return mMaster->mTaskQueue; }
MediaResource* Resource() const { return mMaster->mResource; }
ReaderProxy* Reader() const { return mMaster->mReader; }
const MediaInfo& Info() const { return mMaster->Info(); }
MediaQueue<AudioData>& AudioQueue() const { return mMaster->mAudioQueue; }
@ -2470,8 +2469,7 @@ SeekingState::SeekCompleted()
{
const auto newCurrentTime = CalculateNewCurrentTime();
bool isLiveStream = Resource()->IsLiveStream();
if (newCurrentTime == mMaster->Duration() && !isLiveStream) {
if (newCurrentTime == mMaster->Duration() && !mMaster->mIsLiveStream) {
// Seeked to end of media. Explicitly finish the queues so DECODING
// will transition to COMPLETED immediately. Note we don't do
// this when playing a live stream, since the end of media will advance
@ -2687,7 +2685,6 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
mVideoDecodeSuspended(false),
mVideoDecodeSuspendTimer(mTaskQueue),
mOutputStreamManager(new OutputStreamManager()),
mResource(aDecoder->GetResource()),
mVideoDecodeMode(VideoDecodeMode::Normal),
mIsMSE(aDecoder->IsMSE()),
INIT_MIRROR(mBuffered, TimeIntervals()),
@ -3455,10 +3452,8 @@ MediaDecoderStateMachine::FinishDecodeFirstFrame()
mMediaSink->Redraw(Info().mVideo);
LOG("Media duration %" PRId64 ", "
"transportSeekable=%d, mediaSeekable=%d",
Duration().ToMicroseconds(), mResource->IsTransportSeekable(),
mMediaSeekable);
LOG("Media duration %" PRId64 ", mediaSeekable=%d",
Duration().ToMicroseconds(), mMediaSeekable);
// Get potentially updated metadata
mReader->ReadUpdatedMetadata(mInfo.ptr());

View File

@ -219,10 +219,15 @@ public:
OwnerThread()->DispatchStateChange(r.forget());
}
// Drop reference to mResource. Only called during shutdown dance.
void BreakCycles() {
MOZ_ASSERT(NS_IsMainThread());
mResource = nullptr;
void DispatchIsLiveStream(bool aIsLiveStream)
{
RefPtr<MediaDecoderStateMachine> self = this;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
"MediaDecoderStateMachine::DispatchIsLiveStream",
[self, aIsLiveStream]() {
self->mIsLiveStream = aIsLiveStream;
});
OwnerThread()->DispatchStateChange(r.forget());
}
TimedMetadataEventSource& TimedMetadataEvent() {
@ -584,6 +589,8 @@ private:
bool mCanPlayThrough = false;
bool mIsLiveStream = false;
// True if we shouldn't play our audio (but still write it to any capturing
// streams). When this is true, the audio thread will never start again after
// it has stopped.
@ -633,9 +640,6 @@ private:
// Data about MediaStreams that are being fed by the decoder.
const RefPtr<OutputStreamManager> mOutputStreamManager;
// Media data resource from the decoder.
RefPtr<MediaResource> mResource;
// Track the current video decode mode.
VideoDecodeMode mVideoDecodeMode;

View File

@ -10,7 +10,6 @@
#include "nsIChannel.h"
#include "nsIURI.h"
#include "nsISeekableStream.h"
#include "nsIStreamingProtocolController.h"
#include "nsIStreamListener.h"
#include "nsIChannelEventSink.h"
#include "nsIInterfaceRequestor.h"
@ -244,12 +243,6 @@ public:
*/
virtual nsresult GetCachedRanges(MediaByteRangeSet& aRanges) = 0;
// Returns true if the resource is a live stream.
virtual bool IsLiveStream()
{
return GetLength() == -1;
}
virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
return 0;
}
@ -336,6 +329,9 @@ public:
return nullptr;
}
// Returns true if the resource is a live stream.
bool IsLiveStream() { return GetLength() == -1; }
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
{
// Might be useful to track in the future:

View File

@ -21,8 +21,6 @@ public:
{
}
MediaResource* GetResource() const override final;
void Shutdown() override;
// Returns true if the HLS backend is pref'ed on.
@ -43,6 +41,8 @@ public:
void Resume() override;
private:
MediaResource* GetResource() const override final;
MediaDecoderStateMachine* CreateStateMachine();
bool CanPlayThroughImpl() override final
@ -52,6 +52,8 @@ private:
return true;
}
bool IsLiveStream() override final { return false; }
RefPtr<HLSResource> mResource;
};

View File

@ -81,11 +81,6 @@ public:
bool IsTransportSeekable() override { return true; }
bool IsLiveStream() override
{
return false;
}
java::GeckoHLSResourceWrapper::GlobalRef GetResourceWrapper() {
return mHLSResourceWrapper;
}

View File

@ -152,12 +152,21 @@ VideoDecoderChild::InitIPDL(const VideoInfo& aVideoInfo,
{
RefPtr<VideoDecoderManagerChild> manager =
VideoDecoderManagerChild::GetSingleton();
// If the manager isn't available, then don't initialize mIPDLSelfRef and
// The manager isn't available because VideoDecoderManagerChild has been
// initialized with null end points and we don't want to decode video on GPU
// process anymore. Return false here so that we can fallback to other PDMs.
if (!manager) {
return false;
}
// The manager doesn't support sending messages because we've just crashed
// and are working on reinitialization. Don't initialize mIPDLSelfRef and
// leave us in an error state. We'll then immediately reject the promise when
// Init() is called and the caller can try again. Hopefully by then the new
// manager is ready, or we've notified the caller of it being no longer
// available. If not, then the cycle repeats until we're ready.
if (!manager || !manager->CanSend()) {
if (!manager->CanSend()) {
return true;
}

View File

@ -27,8 +27,6 @@ class MediaSourceDecoder : public MediaDecoder
public:
explicit MediaSourceDecoder(MediaDecoderInit& aInit);
MediaResource* GetResource() const override final;
nsresult Load(nsIPrincipal* aPrincipal);
media::TimeIntervals GetSeekable() override;
media::TimeIntervals GetBuffered() override;
@ -64,10 +62,12 @@ public:
void NotifyInitDataArrived();
private:
MediaResource* GetResource() const override final;
MediaDecoderStateMachine* CreateStateMachine();
void DoSetMediaSourceDuration(double aDuration);
media::TimeInterval ClampIntervalToEnd(const media::TimeInterval& aInterval);
bool CanPlayThroughImpl() override;
bool IsLiveStream() override final { return !mEnded; }
RefPtr<MediaSourceResource> mResource;

View File

@ -59,11 +59,6 @@ public:
bool IsTransportSeekable() override { return true; }
bool IsLiveStream() override
{
MonitorAutoLock mon(mMonitor);
return !mEnded;
}
void SetEnded(bool aEnded)
{
MonitorAutoLock mon(mMonitor);

View File

@ -70,7 +70,7 @@ install:: ../js-config.h
#
install::
$(call py_action,process_install_manifest,--no-remove --no-symlinks $(DESTDIR)$(includedir) $(DEPTH)/_build_manifests/install/dist_include)
$(call py_action,process_install_manifest,--track install_dist_include.track --no-symlinks $(DESTDIR)$(includedir) $(DEPTH)/_build_manifests/install/dist_include)
#
# END SpiderMonkey header installation

0
js/src/moz.build Normal file → Executable file
View File

16
js/src/vm/Time.cpp Normal file → Executable file
View File

@ -269,8 +269,10 @@ PRMJ_FormatTime(char* buf, int buflen, const char* fmt, PRMJTime* prtm)
int fake_tm_year = 0;
#ifdef XP_WIN
_invalid_parameter_handler oldHandler;
#ifndef __MINGW32__
int oldReportMode;
#endif
#endif // __MINGW32__
#endif //XP_WIN
memset(&a, 0, sizeof(struct tm));
@ -343,15 +345,23 @@ PRMJ_FormatTime(char* buf, int buflen, const char* fmt, PRMJTime* prtm)
#ifdef XP_WIN
oldHandler = _set_invalid_parameter_handler(PRMJ_InvalidParameterHandler);
#ifndef __MINGW32__
/*
* MinGW doesn't have _CrtSetReportMode and defines it to be a no-op.
* We ifdef it off to avoid warnings about unused variables
*/
oldReportMode = _CrtSetReportMode(_CRT_ASSERT, 0);
#endif
#endif // __MINGW32__
#endif // XP_WIN
result = strftime(buf, buflen, fmt, &a);
#ifdef XP_WIN
_set_invalid_parameter_handler(oldHandler);
#ifndef __MINGW32__
_CrtSetReportMode(_CRT_ASSERT, oldReportMode);
#endif
#endif // __MINGW32__
#endif // XP_WIN
if (fake_tm_year && result) {
char real_year[16];

View File

@ -1311,6 +1311,27 @@ nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver,
return array.RemoveElement(aObserver);
}
void
nsRefreshDriver::PostScrollEvent(mozilla::Runnable* aScrollEvent)
{
mScrollEvents.AppendElement(aScrollEvent);
EnsureTimerStarted();
}
void
nsRefreshDriver::DispatchScrollEvents()
{
// Scroll events are one-shot, so after running them we can drop them.
// However, dispatching a scroll event can potentially cause more scroll
// events to be posted, so we move the initial set into a temporary array
// first. (Newly posted scroll events will be dispatched on the next tick.)
ScrollEventArray events;
events.SwapElements(mScrollEvents);
for (auto& event : events) {
event->Run();
}
}
void
nsRefreshDriver::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver)
{
@ -1817,7 +1838,7 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
mWarningThreshold = 1;
nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) {
if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0 && mScrollEvents.Length() == 0)) {
// Things are being destroyed, or we no longer have any observers.
// We don't want to stop the timer when observers are initially
// removed, because sometimes observers can be added and removed
@ -1876,6 +1897,7 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
DispatchAnimationEvents();
DispatchPendingEvents();
RunFrameRequestCallbacks(aNowTime);
DispatchScrollEvents();
if (mPresContext && mPresContext->GetPresShell()) {
Maybe<AutoProfilerTracing> tracingStyleFlush;

View File

@ -37,6 +37,7 @@ class nsIRunnable;
namespace mozilla {
class RefreshDriverTimer;
class Runnable;
namespace layout {
class VsyncChild;
} // namespace layout
@ -125,6 +126,9 @@ public:
bool RemoveRefreshObserver(nsARefreshObserver *aObserver,
mozilla::FlushType aFlushType);
void PostScrollEvent(mozilla::Runnable* aScrollEvent);
void DispatchScrollEvents();
/**
* Add an observer that will be called after each refresh. The caller
* must remove the observer before it is deleted. This does not trigger
@ -362,6 +366,7 @@ public:
private:
typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
typedef nsTArray<RefPtr<mozilla::Runnable>> ScrollEventArray;
typedef nsTHashtable<nsISupportsHashKey> RequestTable;
struct ImageStartData {
ImageStartData()
@ -468,6 +473,7 @@ private:
RequestTable mRequests;
ImageStartTable mStartTable;
AutoTArray<nsCOMPtr<nsIRunnable>, 16> mEarlyRunners;
ScrollEventArray mScrollEvents;
struct PendingEvent {
nsCOMPtr<nsINode> mTarget;

View File

@ -176,7 +176,6 @@ static void Shutdown();
#include "mozilla/dom/power/PowerManagerService.h"
#include "mozilla/dom/time/TimeService.h"
#include "StreamingProtocolService.h"
#include "nsIPresentationService.h"
@ -199,7 +198,6 @@ using mozilla::dom::workers::ServiceWorkerManager;
using mozilla::dom::workers::WorkerDebuggerManager;
using mozilla::dom::UDPSocketChild;
using mozilla::dom::time::TimeService;
using mozilla::net::StreamingProtocolControllerService;
using mozilla::gmp::GeckoMediaPluginService;
using mozilla::dom::NotificationTelemetryService;
@ -279,8 +277,6 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIPowerManagerService,
PowerManagerService::GetInstance)
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITimeService,
TimeService::GetInstance)
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIStreamingProtocolControllerService,
StreamingProtocolControllerService::GetInstance)
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIMediaManagerService,
MediaManager::GetInstance)
@ -686,7 +682,6 @@ NS_DEFINE_NAMED_CID(NS_POWERMANAGERSERVICE_CID);
NS_DEFINE_NAMED_CID(OSFILECONSTANTSSERVICE_CID);
NS_DEFINE_NAMED_CID(UDPSOCKETCHILD_CID);
NS_DEFINE_NAMED_CID(NS_TIMESERVICE_CID);
NS_DEFINE_NAMED_CID(NS_MEDIASTREAMCONTROLLERSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_MEDIAMANAGERSERVICE_CID);
#ifdef MOZ_WEBSPEECH_TEST_BACKEND
NS_DEFINE_NAMED_CID(NS_FAKE_SPEECH_RECOGNITION_SERVICE_CID);
@ -960,7 +955,6 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = {
{ &kUDPSOCKETCHILD_CID, false, nullptr, UDPSocketChildConstructor },
{ &kGECKO_MEDIA_PLUGIN_SERVICE_CID, true, nullptr, GeckoMediaPluginServiceConstructor },
{ &kNS_TIMESERVICE_CID, false, nullptr, nsITimeServiceConstructor },
{ &kNS_MEDIASTREAMCONTROLLERSERVICE_CID, false, nullptr, nsIStreamingProtocolControllerServiceConstructor },
{ &kNS_MEDIAMANAGERSERVICE_CID, false, nullptr, nsIMediaManagerServiceConstructor },
#ifdef ACCESSIBILITY
{ &kNS_ACCESSIBILITY_SERVICE_CID, false, nullptr, CreateA11yService },
@ -1089,7 +1083,6 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
{ OSFILECONSTANTSSERVICE_CONTRACTID, &kOSFILECONSTANTSSERVICE_CID },
{ "@mozilla.org/udp-socket-child;1", &kUDPSOCKETCHILD_CID },
{ TIMESERVICE_CONTRACTID, &kNS_TIMESERVICE_CID },
{ MEDIASTREAMCONTROLLERSERVICE_CONTRACTID, &kNS_MEDIASTREAMCONTROLLERSERVICE_CID },
{ MEDIAMANAGERSERVICE_CONTRACTID, &kNS_MEDIAMANAGERSERVICE_CID },
#ifdef ACCESSIBILITY
{ "@mozilla.org/accessibilityService;1", &kNS_ACCESSIBILITY_SERVICE_CID },

View File

@ -2075,6 +2075,9 @@ ScrollFrameHelper::ScrollFrameHelper(nsContainerFrame* aOuter,
ScrollFrameHelper::~ScrollFrameHelper()
{
if (mScrollEvent) {
mScrollEvent->Revoke();
}
}
/*
@ -4727,26 +4730,19 @@ void ScrollFrameHelper::CurPosAttributeChanged(nsIContent* aContent)
/* ============= Scroll events ========== */
ScrollFrameHelper::ScrollEvent::ScrollEvent(ScrollFrameHelper* aHelper)
: mHelper(aHelper)
: Runnable("ScrollFrameHelper::ScrollEvent")
, mHelper(aHelper)
{
mDriver = mHelper->mOuter->PresContext()->RefreshDriver();
mDriver->AddRefreshObserver(this, FlushType::Layout);
mHelper->mOuter->PresContext()->RefreshDriver()->PostScrollEvent(this);
}
ScrollFrameHelper::ScrollEvent::~ScrollEvent()
NS_IMETHODIMP
ScrollFrameHelper::ScrollEvent::Run()
{
if (mDriver) {
mDriver->RemoveRefreshObserver(this, FlushType::Layout);
mDriver = nullptr;
if (mHelper) {
mHelper->FireScrollEvent();
}
}
void
ScrollFrameHelper::ScrollEvent::WillRefresh(mozilla::TimeStamp aTime)
{
mDriver->RemoveRefreshObserver(this, FlushType::Layout);
mDriver = nullptr;
mHelper->FireScrollEvent();
return NS_OK;
}
void
@ -4754,6 +4750,7 @@ ScrollFrameHelper::FireScrollEvent()
{
AutoProfilerTracing tracing("Paint", "FireScrollEvent");
MOZ_ASSERT(mScrollEvent);
mScrollEvent->Revoke();
mScrollEvent = nullptr;
ActiveLayerTracker::SetCurrentScrollHandlerFrame(mOuter);

View File

@ -100,36 +100,30 @@ public:
/**
* This class handles the dispatching of scroll events to content.
*
* nsRefreshDriver maintains three lists of refresh observers, one for each
* flush type: FlushType::Style, FlushType::Layout, and FlushType::Display.
* Scroll events are posted to the refresh driver via
* nsRefreshDriver::PostScrollEvent(), and they are fired during a refresh
* driver tick, after running requestAnimationFrame callbacks but before
* the style flush. This allows rAF callbacks to perform scrolling and have
* that scrolling be reflected on the same refresh driver tick, while at
* the same time allowing scroll event listeners to make style changes and
* have those style changes be reflected on the same refresh driver tick.
*
* During a tick, it runs through each list of observers, in order, and runs
* them. To iterate over each list, it uses an EndLimitedIterator, which is
* designed to iterate only over elements present when the iterator was
* created, not elements added afterwards. This means that, for a given flush
* type, a refresh observer added during the execution of another refresh
* observer of that flush type, will not run until the next tick.
* ScrollEvents cannot be refresh observers, because none of the existing
* categories of refresh observers (FlushType::Style, FlushType::Layout,
* and FlushType::Display) are run at the desired time in a refresh driver
* tick. They behave similarly to refresh observers in that their presence
* causes the refresh driver to tick.
*
* During main-thread animation-driven scrolling, ScrollEvents are *posted*
* by AsyncScroll::WillRefresh(). AsyncScroll registers itself as a FlushType::Style
* refresh observer.
*
* Posting a scroll event, as of bug 1250550, registers a FlushType::Layout
* refresh observer, which *fires* the event when run. This allows the event
* to be fired to content in the same refresh driver tick as it is posted.
* This is an important invariant to maintain to reduce scroll event latency
* for main-thread scrolling.
* ScrollEvents are one-shot runnables; the refresh driver drops them after
* running them.
*/
class ScrollEvent : public nsARefreshObserver {
class ScrollEvent : public Runnable {
public:
NS_INLINE_DECL_REFCOUNTING(ScrollEvent, override)
explicit ScrollEvent(ScrollFrameHelper *helper);
void WillRefresh(mozilla::TimeStamp aTime) override;
protected:
virtual ~ScrollEvent();
NS_DECL_NSIRUNNABLE
explicit ScrollEvent(ScrollFrameHelper* aHelper);
void Revoke() { mHelper = nullptr; }
private:
ScrollFrameHelper *mHelper;
RefPtr<nsRefreshDriver> mDriver;
ScrollFrameHelper* mHelper;
};
class AsyncScrollPortEvent : public Runnable {

View File

@ -151,7 +151,7 @@ static LogState *createLogState()
{
size_t i;
for (i = 0; i < sizeof(openLogTable); i++) {
for (i = 0; i < MAX_OPEN_LOGS; i++) {
if (openLogTable[i] == NULL) {
openLogTable[i] = calloc(1, sizeof(LogState));
openLogTable[i]->fakeFd = FAKE_FD_BASE + i;

View File

@ -34,6 +34,26 @@ public:
#define MOZ_ALIGNOF(T) mozilla::AlignmentFinder<T>::alignment
namespace detail {
template<typename T>
struct AlignasHelper
{
T mT;
};
} // namespace detail
/*
* Use this instead of alignof to align struct field as if it is inside
* a struct. On some platforms, there exist types which have different
* alignment between when it is used on its own and when it is used on
* a struct field.
*
* Known examples are 64bit types (uint64_t, double) on 32bit Linux,
* where they have 8byte alignment on their own, and 4byte alignment
* when in struct.
*/
#define MOZ_ALIGNAS_IN_STRUCT(T) alignas(mozilla::detail::AlignasHelper<T>)
/*
* Declare the MOZ_ALIGNED_DECL macro for declaring aligned types.
*

View File

@ -85,7 +85,7 @@ struct Nothing { };
template<class T>
class MOZ_NON_PARAM MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS Maybe
{
alignas(T) unsigned char mStorage[sizeof(T)];
MOZ_ALIGNAS_IN_STRUCT(T) unsigned char mStorage[sizeof(T)];
char mIsSome; // not bool -- guarantees minimal space consumption
// GCC fails due to -Werror=strict-aliasing if |mStorage| is directly cast to

View File

@ -632,7 +632,9 @@ pref("media.mediadrm-widevinecdm.visible", true);
pref("media.eme.enabled", true);
#endif
#ifdef NIGHTLY_BUILD
pref("media.hls.enabled", true);
#endif
// Whether to suspend decoding of videos in background tabs.
pref("media.suspend-bkgnd-video.enabled", true);

View File

@ -27,7 +27,6 @@
android:layout_marginBottom="@dimen/activity_stream_base_margin"
android:layout_gravity="center"
tools:background="@drawable/favicon_globe" />
<!-- todo: overrideScaleType, scaleType=fitCnetetr -->
</FrameLayout>

View File

@ -5,12 +5,16 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/tools">
<!-- The default visibilities are set in code. -->
<!-- The default visibilities are set in code.
centerInside will center smaller favicons and draw a colored border around them. -->
<org.mozilla.gecko.widget.FaviconView
android:id="@+id/favicon_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
gecko:enableRoundCorners="false"
gecko:overrideScaleType="false"
android:scaleType="centerInside"
/>
<ImageView

View File

@ -7,6 +7,7 @@
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/activity_stream_base_margin">
<!-- centerInside will center smaller favicons and draw a colored border around them. -->
<org.mozilla.gecko.widget.FaviconView
android:id="@+id/favicon"
android:layout_width="match_parent"
@ -16,6 +17,8 @@
android:scaleType="centerInside"
gecko:overrideScaleType="false" />
<!-- scrollHorizontally=false allows drags on the TextView to scroll the ViewPager.
See https://stackoverflow.com/a/18171834/2219998 -->
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
@ -24,12 +27,12 @@
android:padding="5dp"
android:drawablePadding="2dp"
android:maxLines="1"
android:singleLine="true"
android:ellipsize="end"
android:gravity="center"
android:textColor="@android:color/white"
android:textSize="12sp"
android:layout_gravity="bottom"
android:scrollHorizontally="false"
tools:text="Lorem Ipsum here is a title" />
</FrameLayout>

View File

@ -52,6 +52,7 @@ public class AppConstants {
public static final boolean feature19Plus = MIN_SDK_VERSION >= 19 || (MAX_SDK_VERSION >= 19 && Build.VERSION.SDK_INT >= 19);
public static final boolean feature20Plus = MIN_SDK_VERSION >= 20 || (MAX_SDK_VERSION >= 20 && Build.VERSION.SDK_INT >= 20);
public static final boolean feature21Plus = MIN_SDK_VERSION >= 21 || (MAX_SDK_VERSION >= 21 && Build.VERSION.SDK_INT >= 21);
public static final boolean feature23Plus = MIN_SDK_VERSION >= 23 || (MAX_SDK_VERSION >= 23 && Build.VERSION.SDK_INT >= 23);
public static final boolean feature24Plus = MIN_SDK_VERSION >= 24 || (MAX_SDK_VERSION >= 24 && Build.VERSION.SDK_INT >= 24);
public static final boolean feature26Plus = MIN_SDK_VERSION >= 26 || (MAX_SDK_VERSION >= 26 && Build.VERSION.SDK_INT >= 26);

View File

@ -28,7 +28,12 @@ public static final String MOZ_LEANPLUM_SDK_CLIENTID =
null;
//#endif
public static final String MOZ_MMA_SENDER_ID = "242693410970";
public static final String MOZ_MMA_SENDER_ID =
//#ifdef MOZ_MMA_GCM_SENDERID
"@MOZ_MMA_GCM_SENDERID@";
//#else
null;
//#endif
public static MmaInterface getMma() {
//#ifdef MOZ_ANDROID_MMA

View File

@ -955,6 +955,7 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
'sync/repositories/android/BookmarksRepository.java',
'sync/repositories/android/BookmarksRepositorySession.java',
'sync/repositories/android/BookmarksSessionHelper.java',
'sync/repositories/android/BookmarksValidationRepository.java',
'sync/repositories/android/BrowserContractHelpers.java',
'sync/repositories/android/CachedSQLiteOpenHelper.java',
'sync/repositories/android/ClientsDatabase.java',
@ -1067,6 +1068,7 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
'sync/stage/ServerSyncStage.java',
'sync/stage/SyncClientsEngineStage.java',
'sync/stage/UploadMetaGlobalStage.java',
'sync/stage/ValidateBookmarksSyncStage.java',
'sync/stage/VersionedServerSyncStage.java',
'sync/SyncConfiguration.java',
'sync/SyncConfigurationException.java',
@ -1095,6 +1097,10 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
'sync/UnexpectedJSONException.java',
'sync/UnknownSynchronizerConfigurationVersionException.java',
'sync/Utils.java',
'sync/validation/BookmarkValidationResults.java',
'sync/validation/BookmarkValidator.java',
'sync/validation/CollectionValidator.java',
'sync/validation/ValidationResults.java',
'tokenserver/TokenServerClient.java',
'tokenserver/TokenServerClientDelegate.java',
'tokenserver/TokenServerException.java',

View File

@ -60,6 +60,7 @@ def _defines():
DEFINES[var] = 1
for var in ('MOZ_ANDROID_GCM_SENDERID',
'MOZ_MMA_GCM_SENDERID',
'MOZ_ANDROID_MAX_SDK_VERSION',
'MOZ_ANDROID_MIN_SDK_VERSION',
'MOZ_PKG_SPECIAL',

View File

@ -2767,7 +2767,22 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
compiledStatement.clearBindings();
compiledStatement.bindLong(1, syncVersion); // NB: 1-based index.
compiledStatement.bindString(2, guid);
changed += compiledStatement.executeUpdateDelete();
// We expect this to be 1.
final int didUpdate = compiledStatement.executeUpdateDelete();
// These strong assertions are here to help figure out root cause of Bug 1392078.
// This will throw if there are duplicate GUIDs present.
if (didUpdate > 1) {
throw new IllegalStateException("Modified more than a single GUID during syncVersion update");
}
// This will throw if the requested GUID is missing from the database.
if (didUpdate == 0) {
throw new IllegalStateException("Expected to modify syncVersion for a guid, but did not");
}
changed += didUpdate;
}
markWriteSuccessful(db);

View File

@ -35,7 +35,7 @@ public class TelemetrySyncPingBuilder extends TelemetryLocalPingBuilder {
final TelemetryStageCollector stage = stages.get(stageName);
// Skip stages that did nothing.
if (stage.inbound == 0 && stage.outbound == 0) {
if (stage.inbound == 0 && stage.outbound == 0 && stage.error == null && stage.validation == null) {
continue;
}
@ -83,6 +83,10 @@ public class TelemetrySyncPingBuilder extends TelemetryLocalPingBuilder {
if (stage.error != null) {
stageJSON.put("failureReason", stage.error);
}
// As above for validation too.
if (stage.validation != null) {
stageJSON.put("validation", stage.validation);
}
addUnchecked(engines, stageJSON);
}

View File

@ -8,3 +8,4 @@ MOZ_UPDATER=
MOZ_ANDROID_ANR_REPORTER=
MOZ_ANDROID_SHARED_ID=org.mozilla.firefox.sharedID
MOZ_ANDROID_GCM_SENDERID=965234145045
MOZ_MMA_GCM_SENDERID=242693410970

View File

@ -8,3 +8,4 @@ MOZ_UPDATER=1
MOZ_ANDROID_ANR_REPORTER=1
MOZ_ANDROID_SHARED_ID=org.mozilla.fennec.sharedID
MOZ_ANDROID_GCM_SENDERID=965234145045
MOZ_MMA_GCM_SENDERID=242693410970

View File

@ -8,3 +8,4 @@ MOZ_UPDATER=
MOZ_ANDROID_ANR_REPORTER=
MOZ_ANDROID_SHARED_ID=org.mozilla.firefox.sharedID
MOZ_ANDROID_GCM_SENDERID=965234145045
MOZ_MMA_GCM_SENDERID=242693410970

View File

@ -7,3 +7,4 @@ MOZ_APP_DISPLAYNAME="Fennec `echo $USER | sed 's/-/_/g'`"
MOZ_UPDATER=
MOZ_ANDROID_ANR_REPORTER=
MOZ_ANDROID_GCM_SENDERID=965234145045
MOZ_MMA_GCM_SENDERID=242693410970

View File

@ -90,6 +90,9 @@
case "unwantedBlocked" :
error = "unwanted";
break;
case "harmfulBlocked" :
error = "harmful";
break;
default:
return;
}
@ -123,6 +126,13 @@
el.remove();
}
if (error !== "harmful") {
el = document.getElementById("errorTitleText_harmful");
el.remove();
el = document.getElementById("errorShortDescText_harmful");
el.remove();
}
if (!getOverride()) {
var btn = document.getElementById("ignoreWarningButton");
if (btn) {
@ -131,7 +141,11 @@
}
// Set sitename
document.getElementById(error + "_sitename").textContent = getHostString();
let siteElem = document.getElementById(error + "_sitename");
if (siteElem) {
siteElem.textContent = getHostString();
}
document.title = document.getElementById("errorTitleText_" + error)
.innerHTML;
@ -151,15 +165,17 @@
<h1 id="errorTitleText_phishing" class="errorTitleText">&safeb.blocked.phishingPage.title3;</h1>
<h1 id="errorTitleText_malware" class="errorTitleText">&safeb.blocked.malwarePage.title;</h1>
<h1 id="errorTitleText_unwanted" class="errorTitleText">&safeb.blocked.unwantedPage.title;</h1>
<h1 id="errorTitleText_harmful" class="errorTitleText">&safeb.blocked.harmfulPage.title;</h1>
</div>
<div id="errorLongContent">
<!-- Short Description -->
<div id="errorShortDesc">
<p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc3;</p>
<p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
<p id="errorShortDescText_unwanted">&safeb.blocked.unwantedPage.shortDesc;</p>
<p id="errorShortDescText_harmful">&safeb.blocked.harmfulPage.shortDesc;</p>
</div>
<!-- Long Description -->

View File

@ -14,6 +14,8 @@ import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import org.mozilla.gecko.AppConstants;
public class ActivityUtils {
private ActivityUtils() {
}
@ -39,6 +41,13 @@ public class ActivityUtils {
newVis = View.SYSTEM_UI_FLAG_VISIBLE;
}
if (AppConstants.Versions.feature23Plus) {
// We also have to set SYSTEM_UI_FLAG_LIGHT_STATUS_BAR with to current system ui status
// to support both light and dark status bar.
final int oldVis = window.getDecorView().getSystemUiVisibility();
newVis |= (oldVis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
window.getDecorView().setSystemUiVisibility(newVis);
} else {
window.setFlags(fullscreen ?

View File

@ -23,3 +23,5 @@
<!ENTITY safeb.blocked.unwantedPage.shortDesc "This web page at <span id='unwanted_sitename'/> has been reported to contain unwanted software and has been blocked based on your security preferences.">
<!ENTITY safeb.blocked.unwantedPage.longDesc "Unwanted software pages try to install software that can be deceptive and affect your system in unexpected ways.">
<!ENTITY safeb.blocked.harmfulPage.title "The site ahead may contain malware">
<!ENTITY safeb.blocked.harmfulPage.shortDesc "&brandShortName; blocked this page because it might try to install dangerous apps that steal or delete your information (for example, photos, passwords, messages and credit cards).">

View File

@ -88,7 +88,7 @@ project_flag('MOZ_SWITCHBOARD',
project_flag('MOZ_ANDROID_HLS_SUPPORT',
help='Enable HLS (HTTP Live Streaming) support (currently using the ExoPlayer library)',
default=True)
default=milestone.is_nightly)
option(env='MOZ_ANDROID_ACTIVITY_STREAM',
help='Enable Activity Stream on Android (replacing the default HomePager)',

View File

@ -44,6 +44,7 @@ import org.mozilla.gecko.sync.stage.NoSuchStageException;
import org.mozilla.gecko.sync.stage.PasswordsServerSyncStage;
import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
import org.mozilla.gecko.sync.stage.UploadMetaGlobalStage;
import org.mozilla.gecko.sync.stage.ValidateBookmarksSyncStage;
import org.mozilla.gecko.sync.telemetry.TelemetryCollector;
import org.mozilla.gecko.sync.telemetry.TelemetryStageCollector;
@ -207,6 +208,7 @@ public class GlobalSession implements HttpResponseObserver {
stages.put(Stage.syncRecentHistory, new RecentHistoryServerSyncStage());
stages.put(Stage.syncBookmarks, new BookmarksServerSyncStage());
stages.put(Stage.validateBookmarks, new ValidateBookmarksSyncStage());
stages.put(Stage.syncFormHistory, new FormHistoryServerSyncStage());
stages.put(Stage.syncFullHistory, new HistoryServerSyncStage());

View File

@ -96,6 +96,7 @@ public class SyncConfiguration {
public static final String CLIENTS_COLLECTION_TIMESTAMP = "serverClientsTimestamp"; // When the collection was touched.
public static final String CLIENT_RECORD_TIMESTAMP = "serverClientRecordTimestamp"; // When our record was touched.
public static final String MIGRATION_SENTINEL_CHECK_TIMESTAMP = "migrationSentinelCheckTimestamp"; // When we last looked in meta/fxa_credentials.
public static final String BOOKMARK_VALIDATION_CHECK_TIMESTAMP = "bookmarkValidationCheckTimestamp"; // Last time we considered performing validation
public static final String PREF_CLUSTER_URL = "clusterURL";
public static final String PREF_SYNC_ID = "syncID";
@ -458,6 +459,14 @@ public class SyncConfiguration {
return getPrefs().getLong(SyncConfiguration.MIGRATION_SENTINEL_CHECK_TIMESTAMP, 0L);
}
public void persistLastValidationCheckTimestamp(long timestamp) {
getEditor().putLong(SyncConfiguration.BOOKMARK_VALIDATION_CHECK_TIMESTAMP, timestamp).commit();
}
public long getLastValidationCheckTimestamp() {
return getPrefs().getLong(SyncConfiguration.BOOKMARK_VALIDATION_CHECK_TIMESTAMP, 0L);
}
public void purgeCryptoKeys() {
if (collectionKeys != null) {
collectionKeys.clear();

View File

@ -27,7 +27,7 @@ public class ConfigurableServer15Repository extends Server15Repository {
private final long batchLimit;
private final ServerSyncStage.MultipleBatches multipleBatches;
private final ServerSyncStage.HighWaterMark highWaterMark;
private final boolean forceFullFetch;
public ConfigurableServer15Repository(
String collection,
long syncDeadline,
@ -39,7 +39,8 @@ public class ConfigurableServer15Repository extends Server15Repository {
String sort,
ServerSyncStage.MultipleBatches multipleBatches,
ServerSyncStage.HighWaterMark highWaterMark,
RepositoryStateProvider stateProvider) throws URISyntaxException {
RepositoryStateProvider stateProvider,
boolean forceFullFetch) throws URISyntaxException {
super(
collection,
syncDeadline,
@ -53,6 +54,7 @@ public class ConfigurableServer15Repository extends Server15Repository {
this.sortOrder = sort;
this.multipleBatches = multipleBatches;
this.highWaterMark = highWaterMark;
this.forceFullFetch = forceFullFetch;
// Sanity check: let's ensure we're configured correctly. At this point in time, it doesn't make
// sense to use H.W.M. with a non-persistent state provider. This might change if we start retrying
@ -81,4 +83,10 @@ public class ConfigurableServer15Repository extends Server15Repository {
public boolean getAllowHighWaterMark() {
return highWaterMark.equals(ServerSyncStage.HighWaterMark.Enabled);
}
@Override
public boolean getFullFetchForced() {
return forceFullFetch;
}
}

View File

@ -111,6 +111,10 @@ public class Server15Repository extends Repository {
return false;
}
public boolean getFullFetchForced() {
return false;
}
/**
* A point in time by which this repository's session must complete fetch and store operations.
* Particularly pertinent for batching downloads performed by the session (should we fetch

View File

@ -115,6 +115,10 @@ public class Server15RepositorySession extends RepositorySession {
*/
@Override
public long getLastSyncTimestamp() {
if (serverRepository.getFullFetchForced()) {
return 0;
}
if (!serverRepository.getAllowHighWaterMark() || !serverRepository.getSortOrder().equals("oldest")) {
return super.getLastSyncTimestamp();
}

View File

@ -90,7 +90,12 @@ public class VersioningDelegateHelper {
// At this point we can be sure that all records in our guidsMap have been successfully
// uploaded. Move syncVersions forward for each GUID en masse.
final Bundle data = new Bundle();
data.putSerializable(BrowserContract.METHOD_PARAM_DATA, this.localVersionsOfGuids);
final int versionMapSizeBeforeUpdate;
synchronized (this.localVersionsOfGuids) {
data.putSerializable(BrowserContract.METHOD_PARAM_DATA, this.localVersionsOfGuids);
versionMapSizeBeforeUpdate = this.localVersionsOfGuids.size();
}
final Bundle result = context.getContentResolver().call(
repositoryContentUri,
@ -103,11 +108,21 @@ public class VersioningDelegateHelper {
throw new IllegalStateException("Expected to receive a result after decrementing change counters");
}
int changed = (int) result.getSerializable(BrowserContract.METHOD_RESULT);
if (changed != this.localVersionsOfGuids.size()) {
// TODO perhaps not worth throwing, but this shouldn't happen either...
throw new IllegalStateException("Decremented incorrect number of change counters");
final int changed = (int) result.getSerializable(BrowserContract.METHOD_RESULT);
final int versionMapSizeAfterUpdate = this.localVersionsOfGuids.size();
// None of these should happen, but something's clearly amiss. These strong assertions are
// here to help figure out root cause for Bug 1392078.
if (versionMapSizeBeforeUpdate != versionMapSizeAfterUpdate) {
throw new IllegalStateException("Version/guid map changed during syncVersion update");
}
if (changed < versionMapSizeBeforeUpdate) {
throw new IllegalStateException("Updated fewer sync versions than expected");
}
if (changed > versionMapSizeBeforeUpdate) {
throw new IllegalStateException("Updated more sync versions than expected");
}
}

View File

@ -0,0 +1,229 @@
/* 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.repositories.android;
import android.content.Context;
import android.os.SystemClock;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
import org.mozilla.gecko.sync.repositories.InactiveSessionException;
import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
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.delegates.RepositorySessionBeginDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
import org.mozilla.gecko.sync.repositories.domain.Record;
import org.mozilla.gecko.sync.telemetry.TelemetryStageCollector;
import org.mozilla.gecko.sync.validation.BookmarkValidationResults;
import org.mozilla.gecko.sync.validation.BookmarkValidator;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
/**
* This repository fetches all records that are present on the client or server, and runs
* the BookmarkValidator on these, so that we can include that data in telemetry.
*
* This is obviously expensive, so it's worth noting that we don't run it frequently.
* @see org.mozilla.gecko.sync.stage.ValidateBookmarksSyncStage for the set of requirements
* that must be met in order to run validation.
*
* It's mostly concerned with the client's view of the world, and how client propagates that
* view outwards. That is why we're wrapping a regular bookmarks session here, and capturing
* any side-effects that its internal 'fetch' methods will have.
*
* We're not concerned with how client's view of the world is shaped - that is, we're
* not capturing record reconciliation here directly. These sorts of effects will
* (hopefully) be determined from validation results in aggregate.
*
* @see BookmarkValidationResults for the concrete set of problems it checks for.
*/
public class BookmarksValidationRepository extends Repository {
private static final String LOG_TAG = "BookmarksValidationRepository";
protected final ClientsDataDelegate clientsDataDelegate;
private final TelemetryStageCollector parentCollector;
public BookmarksValidationRepository(ClientsDataDelegate clientsDataDelegate, TelemetryStageCollector collector) {
this.clientsDataDelegate = clientsDataDelegate;
this.parentCollector = collector;
}
@Override
public void createSession(RepositorySessionCreationDelegate delegate, Context context) {
delegate.onSessionCreated(new BookmarksValidationRepositorySession(this, context));
}
public class BookmarksValidationRepositorySession extends RepositorySession {
private final ConcurrentLinkedQueue<BookmarkRecord> local = new ConcurrentLinkedQueue<>();
private final ConcurrentLinkedQueue<BookmarkRecord> remote = new ConcurrentLinkedQueue<>();
private long startTime;
private final BookmarksRepositorySession wrappedSession;
public BookmarksValidationRepositorySession(Repository r, Context context) {
super(r);
startTime = SystemClock.elapsedRealtime();
wrappedSession = new BookmarksRepositorySession(r, context);
}
@Override
public long getLastSyncTimestamp() {
return 0;
}
@Override
public void wipe(RepositorySessionWipeDelegate delegate) {
Logger.error(LOG_TAG, "wipe() called on bookmark validator");
throw new UnsupportedOperationException();
}
@Override
public synchronized boolean isActive() {
return this.wrappedSession.isActive();
}
@Override
public void begin(final RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
wrappedSession.begin(new RepositorySessionBeginDelegate() {
@Override
public void onBeginFailed(Exception ex) {
delegate.onBeginFailed(ex);
}
@Override
public void onBeginSucceeded(RepositorySession session) {
delegate.onBeginSucceeded(BookmarksValidationRepositorySession.this);
}
@Override
public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) {
final RepositorySessionBeginDelegate deferred = delegate.deferredBeginDelegate(executor);
return new RepositorySessionBeginDelegate() {
@Override
public void onBeginSucceeded(RepositorySession session) {
if (wrappedSession != session) {
Logger.warn(LOG_TAG, "Got onBeginSucceeded for session " + session + ", not our inner session!");
}
deferred.onBeginSucceeded(BookmarksValidationRepositorySession.this);
}
@Override
public void onBeginFailed(Exception ex) {
deferred.onBeginFailed(ex);
}
@Override
public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) {
return this;
}
};
}
});
}
@Override
public void finish(final RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
wrappedSession.finish(new RepositorySessionFinishDelegate() {
@Override
public void onFinishFailed(Exception ex) {
delegate.onFinishFailed(ex);
}
@Override
public void onFinishSucceeded(RepositorySession session, RepositorySessionBundle bundle) {
delegate.onFinishSucceeded(BookmarksValidationRepositorySession.this, bundle);
}
@Override
public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) {
return this;
}
});
}
private void validateForTelemetry() {
ArrayList<BookmarkRecord> localRecords = new ArrayList<>(local);
ArrayList<BookmarkRecord> remoteRecords = new ArrayList<>(remote);
BookmarkValidationResults results = BookmarkValidator.validateClientAgainstServer(localRecords, remoteRecords);
ExtendedJSONObject o = new ExtendedJSONObject();
o.put("took", SystemClock.elapsedRealtime() - startTime);
o.put("checked", Math.max(localRecords.size(), remoteRecords.size()));
o.put("problems", results.jsonSummary());
Logger.info(LOG_TAG, "Completed validation in " + (SystemClock.elapsedRealtime() - startTime) + " ms");
parentCollector.validation = o;
}
@Override
public void fetchModified(final RepositorySessionFetchRecordsDelegate delegate) {
wrappedSession.fetchAll(new RepositorySessionFetchRecordsDelegate() {
@Override
public void onFetchFailed(Exception ex) {
local.clear();
remote.clear();
delegate.onFetchFailed(ex);
}
@Override
public void onFetchedRecord(Record record) {
local.add((BookmarkRecord)record);
}
@Override
public void onFetchCompleted(long fetchEnd) {
validateForTelemetry();
delegate.onFetchCompleted(fetchEnd);
}
@Override
public void onBatchCompleted() {}
@Override
public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService executor) {
return null;
}
});
}
@Override
public void fetch(String[] guids, RepositorySessionFetchRecordsDelegate delegate) throws InactiveSessionException {
Logger.error(LOG_TAG, "fetch guids[] called on bookmark validator");
throw new UnsupportedOperationException();
}
@Override
public void fetchAll(RepositorySessionFetchRecordsDelegate delegate) {
this.fetchModified(delegate);
}
@Override
public void store(Record record) throws NoStoreDelegateException {
remote.add((BookmarkRecord)record);
}
@Override
public void storeIncomplete() {
super.storeIncomplete();
}
@Override
public void storeDone() {
super.storeDone();
}
}
}

View File

@ -81,7 +81,8 @@ public class BookmarksServerSyncStage extends VersionedServerSyncStage {
BOOKMARKS_SORT,
getAllowedMultipleBatches(),
getAllowedToUseHighWaterMark(),
getRepositoryStateProvider()
getRepositoryStateProvider(),
false
);
}

View File

@ -75,7 +75,8 @@ public class FormHistoryServerSyncStage extends ServerSyncStage {
FORM_HISTORY_SORT,
getAllowedMultipleBatches(),
getAllowedToUseHighWaterMark(),
getRepositoryStateProvider()
getRepositoryStateProvider(),
false
);
}

View File

@ -41,6 +41,8 @@ public interface GlobalSyncStage {
syncFullHistory("history"),
syncFormHistory("forms"),
validateBookmarks,
uploadMetaGlobal,
completed;

View File

@ -93,7 +93,8 @@ public class HistoryServerSyncStage extends ServerSyncStage {
HISTORY_SORT,
getAllowedMultipleBatches(),
getAllowedToUseHighWaterMark(),
getRepositoryStateProvider()
getRepositoryStateProvider(),
false
);
}

View File

@ -94,7 +94,8 @@ public class RecentHistoryServerSyncStage extends HistoryServerSyncStage {
HISTORY_SORT,
getAllowedMultipleBatches(),
getAllowedToUseHighWaterMark(),
getRepositoryStateProvider());
getRepositoryStateProvider(),
false);
}
/**

View File

@ -0,0 +1,204 @@
/* 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 android.content.Context;
import android.database.Cursor;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.MetaGlobalException;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SynchronizerConfiguration;
import org.mozilla.gecko.sync.middleware.BufferingMiddlewareRepository;
import org.mozilla.gecko.sync.middleware.MiddlewareRepository;
import org.mozilla.gecko.sync.middleware.storage.MemoryBufferStorage;
import org.mozilla.gecko.sync.repositories.ConfigurableServer15Repository;
import org.mozilla.gecko.sync.repositories.RecordFactory;
import org.mozilla.gecko.sync.repositories.Repository;
import org.mozilla.gecko.sync.repositories.android.BookmarksValidationRepository;
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecordFactory;
import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
import org.mozilla.gecko.sync.telemetry.TelemetryCollector;
import org.mozilla.gecko.sync.telemetry.TelemetryStageCollector;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.concurrent.TimeUnit;
/**
* This sync stage runs bookmark validation for including in telemetry. Bookmark validation
* is fairly costly, so we don't want to run it frequently, and there are a number of
* checks to make sure it's a good idea to run it.
*
* We only even consider running validation once a day, and during that time we
* make a number of checks to determine if we should run it:
*
* - We only run validation (VALIDATION_PROBABILITY * 100)% of the time (based on Math.random())
* - We must be on nightly (may be relaxed eventually).
* - You must have have fewer than MAX_BOOKMARKS_COUNT bookmarks in your local database
* - There must be enough time left in the sync deadline (at least TIME_REQUIRED_TO_VALIDATE ms).
* - The bookmarks collection must have ran, reported telemetry, and not included an error in
* the telemetry stage.
*/
public class ValidateBookmarksSyncStage extends ServerSyncStage {
protected static final String LOG_TAG = "ValidateBookmarksStage";
private static final String BOOKMARKS_SORT = "oldest";
private static final long BOOKMARKS_BATCH_LIMIT = 5000;
// Maximum number of (local) bookmarks we have where we're still
// willing to run a validation.
private static final long MAX_BOOKMARKS_COUNT = 1000;
// Probability that, given all other requirements are met, we'll run a validation.
private static final double VALIDATION_PROBABILITY = 0.1;
// If we have less than this amount of time left, we don't bother validating.
// Currently 2 minutes, which would be an insane amount of time for a
// validation to take, but it's better to be conservative here.
private static final long TIME_REQUIRED_TO_VALIDATE = TimeUnit.MINUTES.toMillis(2);
// We only even attempt to validate this frequently
private static final long VALIDATION_INTERVAL = TimeUnit.DAYS.toMillis(1);
@Override
protected String getCollection() {
return "bookmarks";
}
@Override
protected String getEngineName() {
return "bookmarks";
}
@Override
public Integer getStorageVersion() {
return VersionConstants.BOOKMARKS_ENGINE_VERSION;
}
/**
* We can't validate without all of the records, so a HWM doesn't make sense.
*
* @return HighWaterMark.Disabled
*/
@Override
protected HighWaterMark getAllowedToUseHighWaterMark() {
return HighWaterMark.Disabled;
}
/**
* Full batching is allowed, because we want all of the records.
*
* @return MultipleBatches.Enabled
*/
@Override
protected MultipleBatches getAllowedMultipleBatches() {
return MultipleBatches.Enabled;
}
@Override
protected Repository getRemoteRepository() throws URISyntaxException {
return new ConfigurableServer15Repository(
getCollection(),
session.getSyncDeadline(),
session.config.storageURL(),
session.getAuthHeaderProvider(),
session.config.infoCollections,
session.config.infoConfiguration,
BOOKMARKS_BATCH_LIMIT,
BOOKMARKS_SORT,
getAllowedMultipleBatches(),
getAllowedToUseHighWaterMark(),
getRepositoryStateProvider(),
true
);
}
@Override
protected Repository getLocalRepository() {
TelemetryStageCollector bookmarkCollector =
this.telemetryStageCollector.getSyncCollector().collectorFor("bookmarks");
return new BookmarksValidationRepository(session.getClientsDelegate(), bookmarkCollector);
}
@Override
protected RecordFactory getRecordFactory() {
return new BookmarkRecordFactory();
}
private long getLocalBookmarkRecordCount() {
final Context context = session.getContext();
final Cursor cursor = context.getContentResolver().query(
BrowserContractHelpers.BOOKMARKS_CONTENT_URI,
new String[] {BrowserContract.Bookmarks._ID},
null, null, null
);
if (cursor == null) {
return -1;
}
try {
return cursor.getCount();
} finally {
cursor.close();
}
}
// This implements the logic described in the header comment.
private boolean shouldValidate() {
if (!AppConstants.NIGHTLY_BUILD) {
return false;
}
final long consideredValidationLast = session.config.getLastValidationCheckTimestamp();
final long now = System.currentTimeMillis();
if (now - consideredValidationLast < VALIDATION_INTERVAL) {
return false;
}
session.config.persistLastValidationCheckTimestamp(now);
if (Math.random() > VALIDATION_PROBABILITY) {
return false;
}
// Note that syncDeadline is relative to the elapsedRealtime clock, not `now`.
final long timeToSyncDeadline = session.getSyncDeadline() - SystemClock.elapsedRealtime();
if (timeToSyncDeadline < TIME_REQUIRED_TO_VALIDATE) {
return false;
}
// See if we'll have somewhere to store the validation results
TelemetryCollector syncCollector = telemetryStageCollector.getSyncCollector();
if (!syncCollector.hasCollectorFor("bookmarks")) {
return false;
}
// And make sure that bookmarks sync didn't hit an error.
TelemetryStageCollector stageCollector = syncCollector.collectorFor("bookmarks");
if (stageCollector.error != null) {
return false;
}
long count = getLocalBookmarkRecordCount();
if (count < 0 || count > MAX_BOOKMARKS_COUNT) {
return false;
}
return true;
}
@Override
protected boolean isEnabled() throws MetaGlobalException {
if (session == null || session.getContext() == null) {
return false;
}
return super.isEnabled() && shouldValidate();
}
}

View File

@ -64,6 +64,10 @@ public class TelemetryCollector {
return collector;
}
public boolean hasCollectorFor(@NonNull String stageName) {
return stageCollectors.containsKey(stageName);
}
public void setRestarted() {
this.didRestart = true;
}

View File

@ -26,6 +26,7 @@ public class TelemetryStageCollector {
public volatile int outboundFailed = 0;
public volatile int reconciled = 0;
public volatile ExtendedJSONObject error = null;
public volatile ExtendedJSONObject validation = null;
public TelemetryStageCollector(TelemetryCollector syncCollector) {
this.syncCollector = syncCollector;

View File

@ -0,0 +1,173 @@
/* 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.validation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Stores data that describes the set of problems found during bookmark validation.
* It stores enough info so that meaningful action to repair or visualize the problems could
* be taken in the future, although at the moment the primary use is to generate data for
* telemetry.
*
* Currently it only holds a subset of what is stored by desktop's implementation.
*/
public class BookmarkValidationResults extends ValidationResults {
/**
* Simple class used to store a pair of guids that refer to a parent/child relationship.
*/
public static class ParentChildPair {
public String parent;
public String child;
public ParentChildPair(String parentID, String childID) {
this.parent = parentID;
this.child = childID;
}
// There are a few cases where it's difficult for the validator to guarantee uniqueness of
// it's complaints, and so it helps to be able to store these in a Map/Set by value.
// The implementations were automatically generated.
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ParentChildPair that = (ParentChildPair) o;
if (!parent.equals(that.parent)) {
return false;
}
return child.equals(that.child);
}
@Override
public int hashCode() {
int result = parent.hashCode();
result = 31 * result + child.hashCode();
return result;
}
}
/**
* True if we saw the root record (the record with a guid of "places") on the server.
*/
public boolean rootOnServer = false;
/**
* List of records where the parent refers to a child that does not exist.
*/
public List<ParentChildPair> missingChildren = new ArrayList<>();
/**
* List of records where the parent refers to a child that was deleted.
*/
public List<ParentChildPair> deletedChildren = new ArrayList<>();
/**
* List of records where the parent was deleted but the child was not.
*/
public List<ParentChildPair> deletedParents = new ArrayList<>();
/**
* List of records with either no parent, or where the parent could not be found.
*/
public List<ParentChildPair> orphans = new ArrayList<>();
/**
* List of records who have the same child listed multiple times in their children array.
*/
public List<ParentChildPair> duplicateChildren = new ArrayList<>();
/**
* List of records who refer to parents that are not folders.
*/
public List<ParentChildPair> parentNotFolder = new ArrayList<>();
/**
* List of server-side records where the child's parentid and parent's children array disagree
* with each-other.
*/
public Set<ParentChildPair> parentChildMismatches = new HashSet<>();
/**
* Map of child guid to list of parent guids for records that show up in multiple parents.
*/
public Map<String, List<String>> multipleParents = new HashMap<>();
/**
* Set of ids of records present on the server but missing from the client.
*/
public Set<String> clientMissing = new HashSet<>();
/**
* Set of ids of records present on the client but missing from the server.
*/
public Set<String> serverMissing = new HashSet<>();
/**
* List of ids of records present on the client, with tombstones on the server.
*/
public Set<String> serverDeleted = new HashSet<>();
/**
* List of ids of items where the child guids differ between client and server.
* Represents sdiff:childGUIDs, and part of structuralDifferences in the summary.
*/
public Set<String> structuralDifferenceChildGUIDs = new HashSet<>();
/**
* List of ids of items where parentIDs differ between client and server.
* Represents sdiff:parentid, and part of structuralDifferences in the summary.
*/
public Set<String> structuralDifferenceParentIDs = new HashSet<>();
/**
* List of ids where the client and server disagree about something not covered by a structural
* difference.
*/
public Set<String> differences = new HashSet<>();
private static void addProp(Map<String, Integer> m, String propName, int count) {
if (count != 0) {
m.put(propName, count);
}
}
public Map<String, Integer> summarizeResults() {
Map<String, Integer> m = new HashMap<>();
if (rootOnServer) {
addProp(m, "rootOnServer", 1);
}
addProp(m, "parentChildMismatches", parentChildMismatches.size());
addProp(m, "missingChildren", missingChildren.size());
addProp(m, "deletedChildren", deletedChildren.size());
addProp(m, "deletedParents", deletedParents.size());
addProp(m, "multipleParents", multipleParents.size());
addProp(m, "orphans", orphans.size());
addProp(m, "duplicateChildren", duplicateChildren.size());
addProp(m, "parentNotFolder", parentNotFolder.size());
addProp(m, "clientMissing", clientMissing.size());
addProp(m, "serverMissing", serverMissing.size());
addProp(m, "serverDeleted", serverDeleted.size());
addProp(m, "sdiff:childGUIDs", structuralDifferenceChildGUIDs.size());
addProp(m, "sdiff:parentid", structuralDifferenceParentIDs.size());
addProp(m, "structuralDifferences", structuralDifferenceParentIDs.size() + structuralDifferenceChildGUIDs.size());
addProp(m, "differences", differences.size());
return m;
}
}

View File

@ -0,0 +1,287 @@
/* 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.validation;
import org.json.simple.JSONArray;
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class BookmarkValidator {
private List<BookmarkRecord> localRecords;
private List<BookmarkRecord> remoteRecords;
private Map<String, BookmarkRecord> remoteGUIDToRecord = new HashMap<>();
/**
* Construct a bookmark validator from local and remote records
* */
public BookmarkValidator(List<BookmarkRecord> localRecords, List<BookmarkRecord> remoteRecords) {
this.localRecords = localRecords;
this.remoteRecords = remoteRecords;
for (BookmarkRecord r : remoteRecords) {
remoteGUIDToRecord.put(r.guid, r);
}
}
private void checkServerFolder(BookmarkRecord r, BookmarkValidationResults results) {
if (r.children == null) {
return;
}
HashSet<String> seenChildGUIDs = new HashSet<>();
for (int i = 0; i < r.children.size(); ++i) {
String childGUID = (String) r.children.get(i);
if (seenChildGUIDs.contains(childGUID)) {
results.duplicateChildren.add(
new BookmarkValidationResults.ParentChildPair(r.guid, childGUID)
);
continue;
}
seenChildGUIDs.add(childGUID);
BookmarkRecord child = remoteGUIDToRecord.get(childGUID);
if (child == null) {
results.missingChildren.add(
new BookmarkValidationResults.ParentChildPair(r.guid, childGUID)
);
continue;
}
List<String> parentsContainingChild = results.multipleParents.get(childGUID);
if (parentsContainingChild == null) {
// Common case, we've never seen a parent with this child before, so we add a
// record indicating we've seen one, and what the id was. If there aren't any
// other parents of this record, we clean this up before returning the results
// to the validator's caller (in cleanupValidationResults).
parentsContainingChild = new ArrayList<>();
parentsContainingChild.add(r.guid);
results.multipleParents.put(childGUID, parentsContainingChild);
} else {
parentsContainingChild.add(r.guid);
}
if (child.deleted) {
results.deletedChildren.add(
new BookmarkValidationResults.ParentChildPair(r.guid, childGUID)
);
continue;
}
if (!child.parentID.equals(r.guid)) {
results.parentChildMismatches.add(
new BookmarkValidationResults.ParentChildPair(r.guid, childGUID)
);
}
}
}
// Check for errors with the parent that aren't covered (or that might only be partially
// covered) by the checks in `checkServerFolder`.
private void checkServerParent(BookmarkRecord record, BookmarkValidationResults results) {
String parentID = record.parentID;
if (parentID.equals("places")) {
// Parent is the places root, so we don't care.
return;
}
BookmarkRecord listedParent = remoteGUIDToRecord.get(parentID);
if (listedParent == null) {
results.orphans.add(
new BookmarkValidationResults.ParentChildPair(parentID, record.guid)
);
return;
}
if (listedParent.deleted) {
results.deletedParents.add(
new BookmarkValidationResults.ParentChildPair(parentID, record.guid)
);
} else if (!listedParent.isFolder()) {
results.parentNotFolder.add(
new BookmarkValidationResults.ParentChildPair(parentID, record.guid)
);
} else {
boolean foundChild = false;
for (int i = 0; i < listedParent.children.size() && !foundChild; ++i) {
String childGUID = (String) listedParent.children.get(i);
foundChild = childGUID.equals(record.guid);
}
if (!foundChild) {
results.parentChildMismatches.add(
new BookmarkValidationResults.ParentChildPair(parentID, record.guid)
);
}
}
}
private void inspectServerRecords(BookmarkValidationResults results) {
for (BookmarkRecord record : remoteRecords) {
if (record.deleted) {
continue;
}
if (record.guid.equals("places")) {
results.rootOnServer = true;
continue;
}
if (record.isFolder()) {
checkServerFolder(record, results);
}
checkServerParent(record, results);
}
}
private static class ClientServerPair {
BookmarkRecord client;
BookmarkRecord server;
ClientServerPair(BookmarkRecord client, BookmarkRecord server) {
this.client = client;
this.server = server;
}
}
// Should return false if one or both are missing (and so further comparisons are meaningless.
// If they're inconsistent, the error should be recorded in `results`.
private boolean checkMissing(ClientServerPair p, BookmarkValidationResults results) {
boolean clientExists = p.client != null && !p.client.deleted;
boolean serverExists = p.server != null && !p.server.deleted;
boolean serverTombstone = p.server != null && p.server.deleted;
if (clientExists && !serverExists) {
if (serverTombstone) {
results.serverDeleted.add(p.client.guid);
} else {
results.serverMissing.add(p.client.guid);
}
} else if (serverExists && !clientExists) {
results.clientMissing.add(p.server.guid);
// Should we distinguish between deleted and missing here? Desktop doesn't,
// so to keep consistent metrics we don't either, but maybe both should?
}
return clientExists && serverExists;
}
// Returns true if it found any structural differences, and records them in `results.
private boolean checkStructuralDifferences(ClientServerPair p, BookmarkValidationResults results) {
boolean sawDiff = false;
if (!p.client.parentID.equals(p.server.parentID)) {
results.structuralDifferenceParentIDs.add(p.client.guid);
// Just record we saw it, since we still want to check the childGUIDs
sawDiff = true;
}
if (p.client.children != null && p.server.children != null) {
if (p.client.children.size() == p.server.children.size()) {
for (int i = 0; i < p.client.children.size(); ++i) {
String clientChildGUID = (String) p.client.children.get(i);
String serverChildGUID = (String) p.server.children.get(i);
if (!clientChildGUID.equals(serverChildGUID)) {
results.structuralDifferenceChildGUIDs.add(p.client.guid);
sawDiff = true;
break;
}
}
} else {
// They have different sizes, so the contents must be different.
sawDiff = true;
results.structuralDifferenceChildGUIDs.add(p.client.guid);
}
}
return sawDiff;
}
// Avoid false positive entries in "differences" caused by our use of congruentWith
private BookmarkRecord normalizeRecord(BookmarkRecord r) {
if (r.collection == null) {
r.collection = "bookmarks";
}
if (r.tags == null) {
// Desktop considers these the same and android doesn't. It's unclear if this is a bug.
r.tags = new JSONArray();
}
return r;
}
private void compareClientWithServer(BookmarkValidationResults results) {
Map<String, ClientServerPair> pairsById = new HashMap<>();
for (BookmarkRecord r : remoteRecords) {
pairsById.put(r.guid, new ClientServerPair(null, normalizeRecord(r)));
}
for (BookmarkRecord r : localRecords) {
if (r.deleted) {
continue;
}
ClientServerPair p = pairsById.get(r.guid);
if (p != null) {
p.client = normalizeRecord(r);
} else {
pairsById.put(r.guid, new ClientServerPair(normalizeRecord(r), null));
}
}
for (Entry<String, ClientServerPair> e : pairsById.entrySet()) {
ClientServerPair p = e.getValue();
if (!checkMissing(p, results)) {
continue;
}
// Skip checking for differences if we see a structural difference, since congruentWith
// checks structural differences as well.
boolean sawStructuralDifference = checkStructuralDifferences(p, results);
if (!sawStructuralDifference &&
(!p.client.congruentWith(p.server) || !p.server.congruentWith(p.client))) {
results.differences.add(p.client.guid);
}
}
}
// Remove anything in the validation results that shouldn't be reported as an error,
// e.g. "multipleParents" with only one item in the list
private void cleanupValidationResults(BookmarkValidationResults results) {
Map<String, List<String>> filteredMultipleParents = new HashMap<>();
for (Entry<String, List<String>> entry : results.multipleParents.entrySet()) {
if (entry.getValue().size() >= 2) {
filteredMultipleParents.put(entry.getKey(), entry.getValue());
}
}
results.multipleParents = filteredMultipleParents;
}
private BookmarkValidationResults validate() {
BookmarkValidationResults results = new BookmarkValidationResults();
inspectServerRecords(results);
compareClientWithServer(results);
cleanupValidationResults(results);
return results;
}
/**
* Instantiate a validator from client and server records, and perform validation.
*/
public static BookmarkValidationResults validateClientAgainstServer(List<BookmarkRecord> client, List<BookmarkRecord> server) {
BookmarkValidator v = new BookmarkValidator(client, server);
return v.validate();
}
/**
* Perform the server-side portion of the validation only.
*/
public static BookmarkValidationResults validateServer(List<BookmarkRecord> server) {
BookmarkValidator v = new BookmarkValidator(new ArrayList<BookmarkRecord>(), server);
BookmarkValidationResults results = new BookmarkValidationResults();
v.inspectServerRecords(results);
v.compareClientWithServer(results);
return v.validate();
}
}

View File

@ -0,0 +1,9 @@
/* 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.validation;
public interface CollectionValidator {
public ValidationResults validate();
}

View File

@ -0,0 +1,39 @@
/* 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.validation;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import java.util.List;
import java.util.Map;
public abstract class ValidationResults {
/**
* Get the problems found by the validator. Must not contain numbers less than or equal to zero.
* Must use the same names for problems as other platforms!
*/
public abstract Map<String, Integer> summarizeResults();
/**
* Get the summary as JSON suitable for including in telemetry
*/
@SuppressWarnings("unchecked")
public JSONArray jsonSummary() {
Map<String, Integer> problems = summarizeResults();
JSONArray result = new JSONArray();
for (Map.Entry<String, Integer> problem : problems.entrySet()) {
JSONObject o = new JSONObject();
o.put("name", problem.getKey());
o.put("count", problem.getValue());
result.add(o);
}
return result;
}
public boolean anyProblemsExist() {
return summarizeResults().size() > 0;
}
}

View File

@ -0,0 +1,165 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.sync.test;
import org.json.simple.JSONArray;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
import org.mozilla.gecko.sync.validation.BookmarkValidationResults;
import org.mozilla.gecko.sync.validation.BookmarkValidator;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@RunWith(TestRunner.class)
public class TestBookmarkValidator {
private List<BookmarkRecord> getDummyRecords() {
List<BookmarkRecord> l = new ArrayList<>();
{
BookmarkRecord r = new BookmarkRecord();
r.guid = "menu";
r.parentID = "places";
r.type = "folder";
r.title = "foo";
r.children = childrenFromGUIDs("aaaaaaaaaaaa", "bbbbbbbbbbbb", "cccccccccccc");
l.add(r);
}
{
BookmarkRecord r = new BookmarkRecord();
r.guid = "aaaaaaaaaaaa";
r.parentID = "menu";
r.type = "folder";
r.title = "stuff";
r.children = new JSONArray();
l.add(r);
}
{
BookmarkRecord r = new BookmarkRecord();
r.guid = "bbbbbbbbbbbb";
r.parentID = "menu";
r.type = "bookmark";
r.title = "bar";
r.bookmarkURI = "http://baz.com";
l.add(r);
}
{
BookmarkRecord r = new BookmarkRecord();
r.guid = "cccccccccccc";
r.parentID = "menu";
r.type = "query";
r.title = "";
r.bookmarkURI = "place:type=6&sort=14&maxResults=10";
l.add(r);
}
return l;
}
@Test
public void testMatching() {
List<BookmarkRecord> client = getDummyRecords();
List<BookmarkRecord> server = getDummyRecords();
BookmarkValidationResults v = BookmarkValidator.validateClientAgainstServer(client, server);
assertFalse(v.anyProblemsExist());
}
@Test
public void testClientMissing() {
List<BookmarkRecord> client = getDummyRecords();
List<BookmarkRecord> server = getDummyRecords();
BookmarkRecord removed = client.remove(client.size() - 1);
BookmarkValidationResults v = BookmarkValidator.validateClientAgainstServer(client, server);
assertTrue(v.anyProblemsExist());
assertEquals(v.clientMissing.size(), 1);
assertTrue(v.clientMissing.contains(removed.guid));
}
@Test
public void testServerMissing() {
// Also tests some other server validation
List<BookmarkRecord> client = getDummyRecords();
List<BookmarkRecord> server = getDummyRecords();
BookmarkRecord removed = server.remove(server.size() - 1);
BookmarkValidationResults v = BookmarkValidator.validateClientAgainstServer(client, server);
assertTrue(v.anyProblemsExist());
assertEquals(v.serverMissing.size(), 1);
assertTrue(v.serverMissing.contains(removed.guid));
assertEquals(v.missingChildren.size(), 1);
assertTrue(v.missingChildren.contains(
new BookmarkValidationResults.ParentChildPair("menu", removed.guid)));
}
@Test
public void testOrphans() {
List<BookmarkRecord> server = getDummyRecords();
BookmarkRecord last = server.get(server.size() - 1);
last.parentID = "asdfasdfasdf";
BookmarkValidationResults v = BookmarkValidator.validateServer(server);
assertTrue(v.anyProblemsExist());
assertEquals(v.orphans.size(), 1);
assertTrue(v.orphans.contains(
new BookmarkValidationResults.ParentChildPair("asdfasdfasdf", last.guid)));
assertEquals(v.parentChildMismatches.size(), 1);
assertTrue(v.parentChildMismatches.contains(
new BookmarkValidationResults.ParentChildPair("menu", last.guid)));
}
@Test
public void testServerDeleted() {
List<BookmarkRecord> client = getDummyRecords();
List<BookmarkRecord> server = getDummyRecords();
BookmarkRecord removed = server.remove(server.size() - 1);
server.add(createTombstone(removed.guid));
BookmarkValidationResults v = BookmarkValidator.validateClientAgainstServer(client, server);
assertTrue(v.anyProblemsExist());
assertEquals(v.serverDeleted.size(), 1);
assertTrue(v.serverDeleted.contains(removed.guid));
assertEquals(v.serverMissing.size(), 0);
assertEquals(v.deletedChildren.size(), 1);
assertTrue(v.deletedChildren.contains(
new BookmarkValidationResults.ParentChildPair(removed.parentID, removed.guid)));
assertEquals(v.missingChildren.size(), 0);
}
@Test
public void testMultipleParents() {
List<BookmarkRecord> server = getDummyRecords();
BookmarkRecord otherFolder = server.get(1);
assertEquals(otherFolder.type, "folder");
otherFolder.children = childrenFromGUIDs("bbbbbbbbbbbb");
BookmarkValidationResults v = BookmarkValidator.validateServer(server);
assertTrue(v.anyProblemsExist());
assertEquals(v.parentChildMismatches.size(), 1);
assertTrue(v.parentChildMismatches.contains(
new BookmarkValidationResults.ParentChildPair(otherFolder.guid, "bbbbbbbbbbbb")));
assertEquals(v.multipleParents.size(), 1);
assertTrue(v.multipleParents.containsKey("bbbbbbbbbbbb"));
List<String> parentGUIDs = v.multipleParents.get("bbbbbbbbbbbb");
assertEquals(parentGUIDs.size(), 2);
assertTrue(parentGUIDs.contains("menu"));
assertTrue(parentGUIDs.contains("aaaaaaaaaaaa"));
}
private BookmarkRecord createTombstone(String guid) {
return new BookmarkRecord(guid, BookmarkRecord.COLLECTION_NAME, 0, true);
}
@SuppressWarnings("unchecked")
private JSONArray childrenFromGUIDs(String... guids) {
JSONArray children = new JSONArray();
children.addAll(Arrays.asList(guids));
return children;
}
}

Some files were not shown because too many files have changed in this diff Show More