Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2017-04-21 11:43:14 +02:00
Родитель 24d2c9df0c 726ee660f9
Коммит 248cf7f04f
169 изменённых файлов: 5457 добавлений и 1325 удалений

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

@ -21,6 +21,16 @@ jobs:
mozilla-aurora: [{hour: 7, minute: 45}] # Buildbot uses minute 40
# No default
- name: nightly-desktop-osx
job:
type: decision-task
treeherder-symbol: Nd-OSX
triggered-by: nightly
target-tasks-method: nightly_macosx
run-on-projects:
- date
when: [] # never (hook only)
- name: nightly-android
job:
type: decision-task

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

@ -4,6 +4,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla-config.h"
#include "AccessibleHandler.h"
import "ocidl.idl";

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

@ -20,7 +20,7 @@ export:: $(MIDL_GENERATED_FILES)
$(MIDL_GENERATED_FILES): midl_done
midl_done: HandlerData.acf HandlerData.idl
$(MIDL) $(MIDL_FLAGS) $(DEFINES) -I $(IA2DIR) -I $(MSAADIR) -Oicf -acf $(srcdir)/HandlerData.acf $(srcdir)/HandlerData.idl
$(MIDL) $(MIDL_FLAGS) $(DEFINES) -I $(topobjdir) -I $(DIST)/include -I $(IA2DIR) -I $(MSAADIR) -Oicf -acf $(srcdir)/HandlerData.acf $(srcdir)/HandlerData.idl
touch $@
INSTALL_TARGETS += midl

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

@ -636,7 +636,8 @@ var FullScreen = {
}
}
ToolbarIconColor.inferFromText();
ToolbarIconColor.inferFromText("fullscreen", aEnterFS);
// For Lion fullscreen, all fullscreen controls are hidden, don't
// bother to touch them. If we don't stop here, the following code

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

@ -247,7 +247,8 @@ var TabsInTitlebar = {
menubar.style.paddingBottom = "";
}
ToolbarIconColor.inferFromText();
ToolbarIconColor.inferFromText("tabsintitlebar", TabsInTitlebar.enabled);
if (CustomizationHandler.isCustomizing()) {
gCustomizeMode.updateLWTStyling();
}

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

@ -5408,8 +5408,6 @@ function setToolbarVisibility(toolbar, isVisible, persist = true) {
PlacesToolbarHelper.init();
BookmarkingUI.onToolbarVisibilityChange();
if (isVisible)
ToolbarIconColor.inferFromText();
}
var TabletModeUpdater = {
@ -7686,7 +7684,7 @@ var gIdentityHandler = {
let tooltiptext = gNavigatorBundle.getString("permissions.remove.tooltip");
button.setAttribute("tooltiptext", tooltiptext);
button.addEventListener("command", () => {
let browser = gBrowser.selectedBrowser;
let browser = gBrowser.selectedBrowser;
// Only resize the window if the reload hint was previously hidden.
this._handleHeightChange(() => this._permissionList.removeChild(container),
this._permissionReloadHint.hasAttribute("hidden"));
@ -8253,18 +8251,25 @@ var MousePosTracker = {
};
var ToolbarIconColor = {
_windowState: {
"active": false,
"fullscreen": false,
"tabsintitlebar": false
},
init() {
this._initialized = true;
window.addEventListener("activate", this);
window.addEventListener("deactivate", this);
window.addEventListener("toolbarvisibilitychange", this);
Services.obs.addObserver(this, "lightweight-theme-styling-update");
// If the window isn't active now, we assume that it has never been active
// before and will soon become active such that inferFromText will be
// called from the initial activate event.
if (Services.focus.activeWindow == window)
this.inferFromText();
if (Services.focus.activeWindow == window) {
this.inferFromText("activate");
}
},
uninit() {
@ -8272,14 +8277,18 @@ var ToolbarIconColor = {
window.removeEventListener("activate", this);
window.removeEventListener("deactivate", this);
window.removeEventListener("toolbarvisibilitychange", this);
Services.obs.removeObserver(this, "lightweight-theme-styling-update");
},
handleEvent(event) {
switch (event.type) {
case "activate":
case "activate": // falls through
case "deactivate":
this.inferFromText();
this.inferFromText(event.type);
break;
case "toolbarvisibilitychange":
this.inferFromText(event.type, event.visible);
break;
}
},
@ -8289,32 +8298,66 @@ var ToolbarIconColor = {
case "lightweight-theme-styling-update":
// inferFromText needs to run after LightweightThemeConsumer.jsm's
// lightweight-theme-styling-update observer.
setTimeout(() => { this.inferFromText(); }, 0);
setTimeout(() => {
this.inferFromText(aTopic);
}, 0);
break;
}
},
inferFromText() {
// a cache of luminance values for each toolbar
// to avoid unnecessary calls to getComputedStyle
_toolbarLuminanceCache: new Map(),
inferFromText(reason, reasonValue) {
if (!this._initialized)
return;
function parseRGB(aColorString) {
let rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
rgb.shift();
return rgb.map(x => parseInt(x));
}
switch (reason) {
case "activate": // falls through
case "deactivate":
this._windowState.active = (reason === "activate");
break;
case "fullscreen":
this._windowState.fullscreen = reasonValue;
break;
case "lightweight-theme-styling-update":
// theme change, we'll need to recalculate all color values
this._toolbarLuminanceCache.clear();
break;
case "toolbarvisibilitychange":
// toolbar changes dont require reset of the cached color values
break;
case "tabsintitlebar":
this._windowState.tabsintitlebar = reasonValue;
break;
}
let toolbarSelector = "#navigator-toolbox > toolbar:not([collapsed=true]):not(#addon-bar)";
if (AppConstants.platform == "macosx")
toolbarSelector += ":not([type=menubar])";
// The getComputedStyle calls and setting the brighttext are separated in
// two loops to avoid flushing layout and making it dirty repeatedly.
let luminances = new Map;
let cachedLuminances = this._toolbarLuminanceCache;
let luminances = new Map();
for (let toolbar of document.querySelectorAll(toolbarSelector)) {
let [r, g, b] = parseRGB(getComputedStyle(toolbar).color);
let luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
// toolbars *should* all have ids, but guard anyway to avoid blowing up
let cacheKey = toolbar.id && toolbar.id + JSON.stringify(this._windowState);
// lookup cached luminance value for this toolbar in this window state
let luminance = cacheKey && cachedLuminances.get(cacheKey);
if (isNaN(luminance)) {
let [r, g, b] = parseRGB(getComputedStyle(toolbar).color);
luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
if (cacheKey) {
cachedLuminances.set(cacheKey, luminance);
}
}
luminances.set(toolbar, luminance);
}

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

@ -254,8 +254,6 @@ var whitelist = new Set([
{file: "resource://gre/modules/addons/AddonLogging.jsm"},
// Bug 1351637
{file: "resource://gre/modules/sdk/bootstrap.js"},
// Bug 1351657
{file: "resource://gre/res/langGroups.properties", platforms: ["macosx"]},
].filter(item =>
("isFromDevTools" in item) == isDevtools &&

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

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"plugin:mozilla/browser-test"
]
};

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

@ -0,0 +1,2 @@
[DEFAULT]
[browser_toolbariconcolor_restyles.js]

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

@ -0,0 +1,43 @@
"use strict";
/**
* Ensure redundant style flushes are not triggered when switching between windows
*/
add_task(function* test_toolbar_element_restyles_on_activation() {
let restyles = {
win1: {},
win2: {}
};
// create a window and snapshot the elementsStyled
let win1 = yield BrowserTestUtils.openNewBrowserWindow();
yield new Promise(resolve => waitForFocus(resolve, win1));
// create a 2nd window and snapshot the elementsStyled
let win2 = yield BrowserTestUtils.openNewBrowserWindow();
yield new Promise(resolve => waitForFocus(resolve, win2));
let utils1 = SpecialPowers.getDOMWindowUtils(win1);
restyles.win1.initial = utils1.elementsRestyled;
let utils2 = SpecialPowers.getDOMWindowUtils(win2);
restyles.win2.initial = utils2.elementsRestyled;
// switch back to 1st window, and snapshot elementsStyled
win1.focus();
restyles.win1.activate = utils1.elementsRestyled;
restyles.win2.deactivate = utils2.elementsRestyled;
// switch back to 2nd window, and snapshot elementsStyled
win2.focus();
restyles.win2.activate = utils2.elementsRestyled;
restyles.win1.deactivate = utils1.elementsRestyled;
is(restyles.win1.activate - restyles.win1.deactivate, 0,
"No elements restyled when re-activating/deactivating a window");
is(restyles.win2.activate - restyles.win2.deactivate, 0,
"No elements restyled when re-activating/deactivating a window");
yield BrowserTestUtils.closeWindow(win1);
yield BrowserTestUtils.closeWindow(win2);
});

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

@ -35,6 +35,7 @@ BROWSER_CHROME_MANIFESTS += [
'content/test/urlbar/browser.ini',
'content/test/webextensions/browser.ini',
'content/test/webrtc/browser.ini',
'content/test/windows/browser.ini',
]
if CONFIG['MOZ_UPDATER']:

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

@ -1859,7 +1859,7 @@ BrowserGlue.prototype = {
Services.prefs.clearUserPref("browser.tabs.animate");
Services.prefs.clearUserPref("browser.fullscreen.animate");
Services.prefs.clearUserPref("browser.tabs.animate");
Services.prefs.clearUserPref("alerts.disableSlidingEffect");
}
// Update the migration version.

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

@ -696,10 +696,6 @@
@RESPATH@/res/fonts/*
@RESPATH@/res/dtd/*
@RESPATH@/res/html/*
#if defined(XP_MACOSX)
; For SafariProfileMigrator.js.
@RESPATH@/res/langGroups.properties
#endif
@RESPATH@/res/language.properties
@RESPATH@/res/entityTables/*
#ifdef XP_MACOSX

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

@ -98,8 +98,7 @@ let customizationListener = {
};
customizationListener.onWidgetAdded =
customizationListener.onWidgetRemoved =
customizationListener.onWidgetMoved =
customizationListener.onWidgetInstanceRemoved = function(aWidgetId) {
customizationListener.onWidgetMoved = function(aWidgetId) {
if (aWidgetId == "zoom-controls") {
for (let window of CustomizableUI.windows) {
updateZoomUI(window.gBrowser.selectedBrowser);

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

@ -10,6 +10,7 @@
#include "mozilla/TextEventDispatcher.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TextInputProcessor.h"
#include "mozilla/widget/IMEData.h"
#include "nsContentUtils.h"
#include "nsIDocShell.h"
#include "nsIWidget.h"
@ -27,12 +28,35 @@ namespace mozilla {
class TextInputProcessorNotification final :
public nsITextInputProcessorNotification
{
typedef IMENotification::SelectionChangeData SelectionChangeData;
typedef IMENotification::SelectionChangeDataBase SelectionChangeDataBase;
typedef IMENotification::TextChangeData TextChangeData;
typedef IMENotification::TextChangeDataBase TextChangeDataBase;
public:
explicit TextInputProcessorNotification(const char* aType)
: mType(aType)
{
}
explicit TextInputProcessorNotification(
const TextChangeDataBase& aTextChangeData)
: mType("notify-text-change")
, mTextChangeData(aTextChangeData)
{
}
explicit TextInputProcessorNotification(
const SelectionChangeDataBase& aSelectionChangeData)
: mType("notify-selection-change")
, mSelectionChangeData(aSelectionChangeData)
{
// SelectionChangeDataBase::mString still refers nsString instance owned
// by aSelectionChangeData. So, this needs to copy the instance.
nsString* string = new nsString(aSelectionChangeData.String());
mSelectionChangeData.mString = string;
}
NS_DECL_ISUPPORTS
NS_IMETHOD GetType(nsACString& aType) override final
@ -41,11 +65,216 @@ public:
return NS_OK;
}
// "notify-text-change" and "notify-selection-change"
NS_IMETHOD GetOffset(uint32_t* aOffset) override final
{
if (NS_WARN_IF(!aOffset)) {
return NS_ERROR_INVALID_ARG;
}
if (IsSelectionChange()) {
*aOffset = mSelectionChangeData.mOffset;
return NS_OK;
}
if (IsTextChange()) {
*aOffset = mTextChangeData.mStartOffset;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
// "notify-selection-change"
NS_IMETHOD GetText(nsAString& aText) override final
{
if (IsSelectionChange()) {
aText = mSelectionChangeData.String();
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetCollapsed(bool* aCollapsed) override final
{
if (NS_WARN_IF(!aCollapsed)) {
return NS_ERROR_INVALID_ARG;
}
if (IsSelectionChange()) {
*aCollapsed = mSelectionChangeData.IsCollapsed();
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetLength(uint32_t* aLength) override final
{
if (NS_WARN_IF(!aLength)) {
return NS_ERROR_INVALID_ARG;
}
if (IsSelectionChange()) {
*aLength = mSelectionChangeData.Length();
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetReversed(bool* aReversed) override final
{
if (NS_WARN_IF(!aReversed)) {
return NS_ERROR_INVALID_ARG;
}
if (IsSelectionChange()) {
*aReversed = mSelectionChangeData.mReversed;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetWritingMode(nsACString& aWritingMode) override final
{
if (IsSelectionChange()) {
WritingMode writingMode = mSelectionChangeData.GetWritingMode();
if (!writingMode.IsVertical()) {
aWritingMode.AssignLiteral("horizontal-tb");
} else if (writingMode.IsVerticalLR()) {
aWritingMode.AssignLiteral("vertical-lr");
} else {
aWritingMode.AssignLiteral("vertical-rl");
}
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetCausedByComposition(bool* aCausedByComposition) override final
{
if (NS_WARN_IF(!aCausedByComposition)) {
return NS_ERROR_INVALID_ARG;
}
if (IsSelectionChange()) {
*aCausedByComposition = mSelectionChangeData.mCausedByComposition;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetCausedBySelectionEvent(
bool* aCausedBySelectionEvent) override final
{
if (NS_WARN_IF(!aCausedBySelectionEvent)) {
return NS_ERROR_INVALID_ARG;
}
if (IsSelectionChange()) {
*aCausedBySelectionEvent = mSelectionChangeData.mCausedBySelectionEvent;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetOccurredDuringComposition(
bool* aOccurredDuringComposition) override final
{
if (NS_WARN_IF(!aOccurredDuringComposition)) {
return NS_ERROR_INVALID_ARG;
}
if (IsSelectionChange()) {
*aOccurredDuringComposition =
mSelectionChangeData.mOccurredDuringComposition;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
// "notify-text-change"
NS_IMETHOD GetRemovedLength(uint32_t* aLength) override final
{
if (NS_WARN_IF(!aLength)) {
return NS_ERROR_INVALID_ARG;
}
if (IsTextChange()) {
*aLength = mTextChangeData.OldLength();
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetAddedLength(uint32_t* aLength) override final
{
if (NS_WARN_IF(!aLength)) {
return NS_ERROR_INVALID_ARG;
}
if (IsTextChange()) {
*aLength = mTextChangeData.NewLength();
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetCausedOnlyByComposition(
bool* aCausedOnlyByComposition) override final
{
if (NS_WARN_IF(!aCausedOnlyByComposition)) {
return NS_ERROR_INVALID_ARG;
}
if (IsTextChange()) {
*aCausedOnlyByComposition = mTextChangeData.mCausedOnlyByComposition;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetIncludingChangesDuringComposition(
bool* aIncludingChangesDuringComposition) override final
{
if (NS_WARN_IF(!aIncludingChangesDuringComposition)) {
return NS_ERROR_INVALID_ARG;
}
if (IsTextChange()) {
*aIncludingChangesDuringComposition =
mTextChangeData.mIncludingChangesDuringComposition;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetIncludingChangesWithoutComposition(
bool* aIncludingChangesWithoutComposition) override final
{
if (NS_WARN_IF(!aIncludingChangesWithoutComposition)) {
return NS_ERROR_INVALID_ARG;
}
if (IsTextChange()) {
*aIncludingChangesWithoutComposition =
mTextChangeData.mIncludingChangesWithoutComposition;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
protected:
~TextInputProcessorNotification() { }
virtual ~TextInputProcessorNotification()
{
if (IsSelectionChange()) {
delete mSelectionChangeData.mString;
mSelectionChangeData.mString = nullptr;
}
}
bool IsTextChange() const
{
return mType.EqualsLiteral("notify-text-change");
}
bool IsSelectionChange() const
{
return mType.EqualsLiteral("notify-selection-change");
}
private:
nsAutoCString mType;
union
{
TextChangeDataBase mTextChangeData;
SelectionChangeDataBase mSelectionChangeData;
};
TextInputProcessorNotification() { }
};
@ -668,6 +897,18 @@ TextInputProcessor::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
case NOTIFY_IME_OF_BLUR:
notification = new TextInputProcessorNotification("notify-blur");
break;
case NOTIFY_IME_OF_TEXT_CHANGE:
notification = new TextInputProcessorNotification(
aNotification.mTextChangeData);
break;
case NOTIFY_IME_OF_SELECTION_CHANGE:
notification = new TextInputProcessorNotification(
aNotification.mSelectionChangeData);
break;
case NOTIFY_IME_OF_POSITION_CHANGE:
notification = new TextInputProcessorNotification(
"notify-position-change");
break;
default:
return NS_ERROR_NOT_IMPLEMENTED;
}
@ -701,8 +942,10 @@ TextInputProcessor::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
NS_IMETHODIMP_(IMENotificationRequests)
TextInputProcessor::GetIMENotificationRequests()
{
// TextInputProcessor::NotifyIME does not require extra change notifications.
return IMENotificationRequests();
// TextInputProcessor should support all change notifications.
return IMENotificationRequests(
IMENotificationRequests::NOTIFY_TEXT_CHANGE |
IMENotificationRequests::NOTIFY_POSITION_CHANGE);
}
NS_IMETHODIMP_(void)

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

@ -4215,7 +4215,7 @@ struct StateTableEntry
};
static constexpr StateTableEntry kManuallyManagedStates[] = {
// none yet; but for example: { "highlight", NS_EVENT_STATE_HIGHLIGHT },
{ "-moz-autofill", NS_EVENT_STATE_AUTOFILL },
{ nullptr, EventStates() },
};

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

@ -59,6 +59,8 @@ function onunload()
SimpleTest.finish();
}
const kIsMac = (navigator.platform.indexOf("Mac") == 0);
var iframe = document.getElementById("iframe");
var childWindow = iframe.contentWindow;
var textareaInFrame;
@ -3786,7 +3788,7 @@ function runCommitCompositionTests()
description + "doCommitWithNullCheck(undefined) should commit the composition with empty string");
}
function runUnloadTests1(aNextTest)
function runUnloadTests1()
{
var description = "runUnloadTests1(): ";
@ -3808,7 +3810,7 @@ function runUnloadTests1(aNextTest)
iframe.removeEventListener("load", arguments.callee, true);
childWindow = iframe.contentWindow;
textareaInFrame = iframe.contentDocument.getElementById("textarea");
setTimeout(aNextTest, 0);
SimpleTest.executeSoon(continueTest);
}, true);
// The composition should be committed internally. So, another TIP should
@ -3848,7 +3850,7 @@ function runUnloadTests1(aNextTest)
iframe.src = "data:text/html,<body>dummy page</body>";
}
function runUnloadTests2(aNextTest)
function runUnloadTests2()
{
var description = "runUnloadTests2(): ";
@ -3870,7 +3872,7 @@ function runUnloadTests2(aNextTest)
iframe.removeEventListener("load", arguments.callee, true);
childWindow = iframe.contentWindow;
textareaInFrame = iframe.contentDocument.getElementById("textarea");
setTimeout(aNextTest, 0);
SimpleTest.executeSoon(continueTest);
}, true);
input.focus();
@ -3911,7 +3913,7 @@ function runUnloadTests2(aNextTest)
iframe.src = "data:text/html,<body>dummy page</body>";
}
function runCallbackTests(aForTests)
function* runCallbackTests(aForTests)
{
var description = "runCallbackTests(aForTests=" + aForTests + "): ";
@ -3921,8 +3923,12 @@ function runCallbackTests(aForTests)
var TIP = createTIP();
var notifications = [];
var callContinueTest = false;
function callback(aTIP, aNotification)
{
if (aTIP == TIP) {
notifications.push(aNotification);
}
switch (aNotification.type) {
case "request-to-commit":
aTIP.commitComposition();
@ -3931,8 +3937,9 @@ function runCallbackTests(aForTests)
aTIP.cancelComposition();
break;
}
if (aTIP == TIP) {
notifications.push(aNotification);
if (callContinueTest) {
callContinueTest = false;
SimpleTest.executeSoon(continueTest);
}
return true;
}
@ -3948,6 +3955,69 @@ function runCallbackTests(aForTests)
}
}
function waitUntilNotificationsReceived()
{
if (notifications.length > 0) {
SimpleTest.executeSoon(continueTest);
} else {
callContinueTest = true;
}
}
function checkPositionChangeNotification(aNotification, aDescription)
{
is(!aNotification || aNotification.type, "notify-position-change",
aDescription + " should cause position change notification");
}
function checkSelectionChangeNotification(aNotification, aDescription, aExpected)
{
is(aNotification.type, "notify-selection-change",
aDescription + " should cause selection change notification");
if (aNotification.type != "notify-selection-change") {
return;
}
is(aNotification.offset, aExpected.offset,
aDescription + " should cause selection change notification whose offset is " + aExpected.offset);
is(aNotification.text, aExpected.text,
aDescription + " should cause selection change notification whose text is '" + aExpected.text + "'");
is(aNotification.collapsed, aExpected.text.length == 0,
aDescription + " should cause selection change notification whose collapsed is " + (aExpected.text.length == 0));
is(aNotification.length, aExpected.text.length,
aDescription + " should cause selection change notification whose length is " + aExpected.text.length);
is(aNotification.reversed, aExpected.reversed || false,
aDescription + " should cause selection change notification whose reversed is " + (aExpected.reversed || false));
is(aNotification.writingMode, aExpected.writingMode || "horizontal-tb",
aDescription + " should cause selection change notification whose writingMode is '" + (aExpected.writingMode || "horizontal-tb"));
is(aNotification.causedByComposition, aExpected.causedByComposition || false,
aDescription + " should cause selection change notification whose causedByComposition is " + (aExpected.causedByComposition || false));
is(aNotification.causedBySelectionEvent, aExpected.causedBySelectionEvent || false,
aDescription + " should cause selection change notification whose causedBySelectionEvent is " + (aExpected.causedBySelectionEvent || false));
is(aNotification.occurredDuringComposition, aExpected.occurredDuringComposition || false,
aDescription + " should cause cause selection change notification whose occurredDuringComposition is " + (aExpected.occurredDuringComposition || false));
}
function checkTextChangeNotification(aNotification, aDescription, aExpected)
{
is(aNotification.type, "notify-text-change",
aDescription + " should cause text change notification");
if (aNotification.type != "notify-text-change") {
return;
}
is(aNotification.offset, aExpected.offset,
aDescription + " should cause text change notification whose offset is " + aExpected.offset);
is(aNotification.removedLength, aExpected.removedLength,
aDescription + " should cause text change notification whose removedLength is " + aExpected.removedLength);
is(aNotification.addedLength, aExpected.addedLength,
aDescription + " should cause text change notification whose addedLength is " + aExpected.addedLength);
is(aNotification.causedOnlyByComposition, aExpected.causedOnlyByComposition || false,
aDescription + " should cause text change notification whose causedOnlyByComposition is " + (aExpected.causedOnlyByComposition || false));
is(aNotification.includingChangesDuringComposition, aExpected.includingChangesDuringComposition || false,
aDescription + " should cause text change notification whose includingChangesDuringComposition is " + (aExpected.includingChangesDuringComposition || false));
is(aNotification.includingChangesWithoutComposition, typeof aExpected.includingChangesWithoutComposition === "boolean" ? aExpected.includingChangesWithoutComposition : true,
aDescription + " should cause text change notification whose includingChangesWithoutComposition is " + (typeof aExpected.includingChangesWithoutComposition === "boolean" ? aExpected.includingChangesWithoutComposition : true));
}
if (aForTests) {
TIP.beginInputTransactionForTests(window, callback);
} else {
@ -3971,15 +4041,94 @@ function runCallbackTests(aForTests)
dumpUnexpectedNotifications(1);
input.focus();
notifications = [];
TIP.setPendingCompositionString("foo");
TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
TIP.flushPendingComposition();
is(notifications.length, 3,
description + "creating composition string 'foo' should cause 3 notifications");
checkTextChangeNotification(notifications[0], description + "creating composition string 'foo'",
{ offset: 0, removedLength: 0, addedLength: 3,
causedOnlyByComposition: true, includingChangesDuringComposition: false, includingChangesWithoutComposition: false});
checkSelectionChangeNotification(notifications[1], description + "creating composition string 'foo'",
{ offset: 3, text: "", causedByComposition: true, occurredDuringComposition: true });
checkPositionChangeNotification(notifications[2], description + "creating composition string 'foo'");
dumpUnexpectedNotifications(3);
notifications = [];
synthesizeMouseAtCenter(input, {});
is(notifications.length, 1,
description + "synthesizeMouseAtCenter(input, {}) during composition should cause a notification");
is(notifications.length, 3,
description + "synthesizeMouseAtCenter(input, {}) during composition should cause 3 notifications");
is(notifications[0].type, "request-to-commit",
description + "synthesizeMouseAtCenter(input, {}) during composition should cause \"request-to-commit\"");
checkTextChangeNotification(notifications[1], description + "synthesizeMouseAtCenter(input, {}) during composition",
{ offset: 0, removedLength: 3, addedLength: 3,
causedOnlyByComposition: true, includingChangesDuringComposition: false, includingChangesWithoutComposition: false});
checkPositionChangeNotification(notifications[2], description + "synthesizeMouseAtCenter(input, {}) during composition");
dumpUnexpectedNotifications(3);
// XXX On macOS, window.moveBy() doesn't cause notify-position-change.
// Investigate this later (although, we cannot notify position change to
// native IME on macOS).
if (!kIsMac) {
input.focus();
notifications = [];
window.moveBy(0, 10);
yield waitUntilNotificationsReceived();
is(notifications.length, 1,
description + "window.moveBy(0, 10) should cause a notification");
checkPositionChangeNotification(notifications[0], description + "window.moveBy(0, 10)");
dumpUnexpectedNotifications(1);
input.focus();
notifications = [];
window.moveBy(10, 0);
yield waitUntilNotificationsReceived();
is(notifications.length, 1,
description + "window.moveBy(10, 0) should cause a notification");
checkPositionChangeNotification(notifications[0], description + "window.moveBy(10, 0)");
dumpUnexpectedNotifications(1);
}
input.focus();
input.value = "abc"
notifications = [];
input.selectionStart = input.selectionEnd = 0;
yield waitUntilNotificationsReceived();
notifications = [];
var rightArrowKeyEvent =
new KeyboardEvent("", { key: "ArrowRight", code: "ArrowRight", keyCode: KeyboardEvent.DOM_VK_RIGHT });
TIP.keydown(rightArrowKeyEvent);
TIP.keyup(rightArrowKeyEvent);
is(notifications.length, 1,
description + "ArrowRight key press should cause a notification");
checkSelectionChangeNotification(notifications[0], description + "ArrowRight key press", { offset: 1, text: "" });
dumpUnexpectedNotifications(1);
notifications = [];
var shiftKeyEvent =
new KeyboardEvent("", { key: "Shift", code: "ShiftLeft", keyCode: KeyboardEvent.DOM_VK_SHIFT });
var leftArrowKeyEvent =
new KeyboardEvent("", { key: "ArrowLeft", code: "ArrowLeft", keyCode: KeyboardEvent.DOM_VK_LEFT });
TIP.keydown(shiftKeyEvent);
TIP.keydown(leftArrowKeyEvent);
TIP.keyup(leftArrowKeyEvent);
TIP.keyup(shiftKeyEvent);
is(notifications.length, 1,
description + "ArrowLeft key press with Shift should cause a notification");
checkSelectionChangeNotification(notifications[0], description + "ArrowLeft key press with Shift", { offset: 0, text: "a", reversed: true });
dumpUnexpectedNotifications(1);
TIP.keydown(rightArrowKeyEvent);
TIP.keyup(rightArrowKeyEvent);
notifications = [];
TIP.keydown(shiftKeyEvent);
TIP.keydown(rightArrowKeyEvent);
TIP.keyup(rightArrowKeyEvent);
TIP.keyup(shiftKeyEvent);
is(notifications.length, 1,
description + "ArrowRight key press with Shift should cause a notification");
checkSelectionChangeNotification(notifications[0], description + "ArrowRight key press with Shift", { offset: 1, text: "b" });
dumpUnexpectedNotifications(1);
notifications = [];
@ -3996,10 +4145,21 @@ function runCallbackTests(aForTests)
dumpUnexpectedNotifications(1);
}
function runTests()
{
textareaInFrame = iframe.contentDocument.getElementById("textarea");
var gTestContinuation = null;
function continueTest()
{
if (!gTestContinuation) {
gTestContinuation = testBody();
}
var ret = gTestContinuation.next();
if (ret.done) {
finish();
}
}
function* testBody()
{
runBeginInputTransactionMethodTests();
runReleaseTests();
runCompositionTests();
@ -4008,13 +4168,16 @@ function runTests()
runKeyTests();
runErrorTests();
runCommitCompositionTests();
runCallbackTests(false);
runCallbackTests(true);
runUnloadTests1(function () {
runUnloadTests2(function () {
finish();
});
});
yield* runCallbackTests(false);
yield* runCallbackTests(true);
yield runUnloadTests1();
yield runUnloadTests2();
}
function runTests()
{
textareaInFrame = iframe.contentDocument.getElementById("textarea");
continueTest();
}
]]>

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

@ -288,6 +288,8 @@ private:
#define NS_EVENT_STATE_LTR NS_DEFINE_EVENT_STATE_MACRO(44)
// Element is rtl (for :dir pseudo-class)
#define NS_EVENT_STATE_RTL NS_DEFINE_EVENT_STATE_MACRO(45)
// Element is filled by Autofill feature.
#define NS_EVENT_STATE_AUTOFILL NS_DEFINE_EVENT_STATE_MACRO(50)
// Event state that is used for values that need to be parsed but do nothing.
#define NS_EVENT_STATE_IGNORE NS_DEFINE_EVENT_STATE_MACRO(63)
@ -306,7 +308,7 @@ private:
// document (e.g. in BindToTree and UnbindFromTree), if that is an
// appropriate thing to do for your state bit.
#define MANUALLY_MANAGED_STATES ( \
mozilla::EventStates() /* none so far */ \
NS_EVENT_STATE_AUTOFILL \
)
// Event states that are managed externally to an element (by the

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

@ -135,8 +135,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndOfAddedTextCache.mContainerNode)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartOfRemovingTextRangeCache.mContainerNode)
tmp->mIMENotificationRequests.mWantUpdates =
IMENotificationRequests::NOTIFY_NOTHING;
tmp->mIMENotificationRequests = nullptr;
tmp->mESM = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -168,6 +167,7 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver)
IMEContentObserver::IMEContentObserver()
: mESM(nullptr)
, mIMENotificationRequests(nullptr)
, mSuppressNotifications(0)
, mPreCharacterDataChangeLength(-1)
, mSendingNotification(NOTIFY_IME_OF_NOTHING)
@ -209,6 +209,7 @@ IMEContentObserver::Init(nsIWidget* aWidget,
mESM->OnStartToObserveContent(this);
mWidget = aWidget;
mIMENotificationRequests = &mWidget->IMENotificationRequestsRef();
if (aWidget->GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN) {
if (!InitWithPlugin(aPresContext, aContent)) {
@ -418,7 +419,7 @@ IMEContentObserver::ObserveEditableNode()
// If this is called before sending NOTIFY_IME_OF_FOCUS (it's possible when
// the editor is reframed before sending NOTIFY_IME_OF_FOCUS asynchronously),
// the update preference of mWidget may be different from after the widget
// the notification requests of mWidget may be different from after the widget
// receives NOTIFY_IME_OF_FOCUS. So, this should be called again by
// OnIMEReceivedFocus() which is called after sending NOTIFY_IME_OF_FOCUS.
if (!mIMEHasFocus) {
@ -433,7 +434,6 @@ IMEContentObserver::ObserveEditableNode()
mEditor->AddEditorObserver(this);
}
mIMENotificationRequests = mWidget->GetIMENotificationRequests();
if (!WasInitializedWithPlugin()) {
// Add selection change listener only when this starts to observe
// non-plugin content since we cannot detect selection changes in
@ -442,16 +442,15 @@ IMEContentObserver::ObserveEditableNode()
NS_ENSURE_TRUE_VOID(selPrivate);
nsresult rv = selPrivate->AddSelectionListener(this);
NS_ENSURE_SUCCESS_VOID(rv);
}
if (mIMENotificationRequests.WantTextChange()) {
// add text change observer
// Add text change observer only when this starts to observe
// non-plugin content since we cannot detect text changes in
// plugins.
mRootContent->AddMutationObserver(this);
}
if (mIMENotificationRequests.WantPositionChanged() && mDocShell) {
// Add scroll position listener and reflow observer to detect position and
// size changes
if (mDocShell) {
// Add scroll position listener and reflow observer to detect position
// and size changes
mDocShell->AddWeakScrollObserver(this);
mDocShell->AddWeakReflowObserver(this);
}
@ -463,6 +462,7 @@ IMEContentObserver::NotifyIMEOfBlur()
// Prevent any notifications to be sent IME.
nsCOMPtr<nsIWidget> widget;
mWidget.swap(widget);
mIMENotificationRequests = nullptr;
// If we hasn't been set focus, we shouldn't send blur notification to IME.
if (!mIMEHasFocus) {
@ -515,11 +515,11 @@ IMEContentObserver::UnregisterObservers()
mFocusedWidget = nullptr;
}
if (mIMENotificationRequests.WantTextChange() && mRootContent) {
if (mRootContent) {
mRootContent->RemoveMutationObserver(this);
}
if (mIMENotificationRequests.WantPositionChanged() && mDocShell) {
if (mDocShell) {
mDocShell->RemoveWeakScrollObserver(this);
mDocShell->RemoveWeakReflowObserver(this);
}
@ -541,8 +541,7 @@ IMEContentObserver::Destroy()
Clear();
mWidget = nullptr;
mIMENotificationRequests.mWantUpdates =
IMENotificationRequests::NOTIFY_NOTHING;
mIMENotificationRequests = nullptr;
if (mESM) {
mESM->OnStopObservingContent(this);
@ -690,6 +689,10 @@ IMEContentObserver::NotifySelectionChanged(nsIDOMDocument* aDOMDocument,
void
IMEContentObserver::ScrollPositionChanged()
{
if (!NeedsPositionChangeNotification()) {
return;
}
MaybeNotifyIMEOfPositionChange();
}
@ -697,6 +700,10 @@ NS_IMETHODIMP
IMEContentObserver::Reflow(DOMHighResTimeStamp aStart,
DOMHighResTimeStamp aEnd)
{
if (!NeedsPositionChangeNotification()) {
return NS_OK;
}
MaybeNotifyIMEOfPositionChange();
return NS_OK;
}
@ -705,6 +712,10 @@ NS_IMETHODIMP
IMEContentObserver::ReflowInterruptible(DOMHighResTimeStamp aStart,
DOMHighResTimeStamp aEnd)
{
if (!NeedsPositionChangeNotification()) {
return NS_OK;
}
MaybeNotifyIMEOfPositionChange();
return NS_OK;
}
@ -786,7 +797,8 @@ bool
IMEContentObserver::OnMouseButtonEvent(nsPresContext* aPresContext,
WidgetMouseEvent* aMouseEvent)
{
if (!mIMENotificationRequests.WantMouseButtonEventOnChar()) {
if (!mIMENotificationRequests ||
!mIMENotificationRequests->WantMouseButtonEventOnChar()) {
return false;
}
if (!aMouseEvent->IsTrusted() ||
@ -873,6 +885,10 @@ IMEContentObserver::CharacterDataWillChange(nsIDocument* aDocument,
"CharacterDataChanged() should've reset "
"mPreCharacterDataChangeLength");
if (!NeedsTextChangeNotification()) {
return;
}
mEndOfAddedTextCache.Clear();
mStartOfRemovingTextRangeCache.Clear();
mPreCharacterDataChangeLength =
@ -891,6 +907,10 @@ IMEContentObserver::CharacterDataChanged(nsIDocument* aDocument,
NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT),
"character data changed for non-text node");
if (!NeedsTextChangeNotification()) {
return;
}
mEndOfAddedTextCache.Clear();
mStartOfRemovingTextRangeCache.Clear();
@ -931,6 +951,10 @@ IMEContentObserver::NotifyContentAdded(nsINode* aContainer,
int32_t aStartIndex,
int32_t aEndIndex)
{
if (!NeedsTextChangeNotification()) {
return;
}
mStartOfRemovingTextRangeCache.Clear();
uint32_t offset = 0;
@ -1003,6 +1027,10 @@ IMEContentObserver::ContentRemoved(nsIDocument* aDocument,
int32_t aIndexInContainer,
nsIContent* aPreviousSibling)
{
if (!NeedsTextChangeNotification()) {
return;
}
mEndOfAddedTextCache.Clear();
nsINode* containerNode = NODE_FROM(aContainer, aDocument);
@ -1061,6 +1089,10 @@ IMEContentObserver::AttributeWillChange(nsIDocument* aDocument,
int32_t aModType,
const nsAttrValue* aNewValue)
{
if (!NeedsTextChangeNotification()) {
return;
}
mPreAttrChangeLength =
ContentEventHandler::GetNativeTextLengthBefore(aElement, mRootContent);
}
@ -1073,6 +1105,10 @@ IMEContentObserver::AttributeChanged(nsIDocument* aDocument,
int32_t aModType,
const nsAttrValue* aOldValue)
{
if (!NeedsTextChangeNotification()) {
return;
}
mEndOfAddedTextCache.Clear();
mStartOfRemovingTextRangeCache.Clear();
@ -1214,6 +1250,15 @@ IMEContentObserver::MaybeNotifyIMEOfTextChange(
FlushMergeableNotifications();
}
void
IMEContentObserver::CancelNotifyingIMEOfTextChange()
{
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMEContentObserver::CancelNotifyingIMEOfTextChange()", this));
mTextChangeData.Clear();
mNeedsToNotifyIMEOfTextChange = false;
}
void
IMEContentObserver::MaybeNotifyIMEOfSelectionChange(
bool aCausedByComposition,
@ -1254,6 +1299,14 @@ IMEContentObserver::MaybeNotifyIMEOfPositionChange()
FlushMergeableNotifications();
}
void
IMEContentObserver::CancelNotifyingIMEOfPositionChange()
{
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMEContentObserver::CancelNotifyIMEOfPositionChange()", this));
mNeedsToNotifyIMEOfPositionChange = false;
}
void
IMEContentObserver::MaybeNotifyCompositionEventHandled()
{
@ -1396,6 +1449,15 @@ IMEContentObserver::FlushMergeableNotifications()
return;
}
// If text change notification and/or position change notification becomes
// unnecessary, let's cancel them.
if (mNeedsToNotifyIMEOfTextChange && !NeedsTextChangeNotification()) {
CancelNotifyingIMEOfTextChange();
}
if (mNeedsToNotifyIMEOfPositionChange && !NeedsPositionChangeNotification()) {
CancelNotifyingIMEOfPositionChange();
}
if (!NeedsToNotifyIMEOfSomething()) {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMEContentObserver::FlushMergeableNotifications(), "
@ -1780,6 +1842,16 @@ IMEContentObserver::IMENotificationSender::SendTextChange()
return;
}
// If text change notification is unnecessary anymore, just cancel it.
if (!mIMEContentObserver->NeedsTextChangeNotification()) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMEContentObserver::IMENotificationSender::"
"SendTextChange(), canceling sending NOTIFY_IME_OF_TEXT_CHANGE",
this));
mIMEContentObserver->CancelNotifyingIMEOfTextChange();
return;
}
MOZ_LOG(sIMECOLog, LogLevel::Info,
("0x%p IMEContentObserver::IMENotificationSender::"
"SendTextChange(), sending NOTIFY_IME_OF_TEXT_CHANGE... "
@ -1821,6 +1893,16 @@ IMEContentObserver::IMENotificationSender::SendPositionChange()
return;
}
// If position change notification is unnecessary anymore, just cancel it.
if (!mIMEContentObserver->NeedsPositionChangeNotification()) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMEContentObserver::IMENotificationSender::"
"SendPositionChange(), canceling sending NOTIFY_IME_OF_POSITION_CHANGE",
this));
mIMEContentObserver->CancelNotifyingIMEOfPositionChange();
return;
}
MOZ_LOG(sIMECOLog, LogLevel::Info,
("0x%p IMEContentObserver::IMENotificationSender::"
"SendPositionChange(), sending NOTIFY_IME_OF_POSITION_CHANGE...", this));

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

@ -99,7 +99,8 @@ public:
bool IsEditorHandlingEventForComposition() const;
bool KeepAliveDuringDeactive() const
{
return mIMENotificationRequests.WantDuringDeactive();
return mIMENotificationRequests &&
mIMENotificationRequests->WantDuringDeactive();
}
nsIWidget* GetWidget() const { return mWidget; }
nsIEditor* GetEditor() const { return mEditor; }
@ -147,12 +148,14 @@ private:
void MaybeNotifyIMEOfFocusSet();
void PostTextChangeNotification();
void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData);
void CancelNotifyingIMEOfTextChange();
void PostSelectionChangeNotification();
void MaybeNotifyIMEOfSelectionChange(bool aCausedByComposition,
bool aCausedBySelectionEvent,
bool aOccurredDuringComposition);
void PostPositionChangeNotification();
void MaybeNotifyIMEOfPositionChange();
void CancelNotifyingIMEOfPositionChange();
void PostCompositionEventHandledNotification();
void NotifyContentAdded(nsINode* aContainer, int32_t aStart, int32_t aEnd);
@ -166,6 +169,16 @@ private:
*/
void UnregisterObservers();
void FlushMergeableNotifications();
bool NeedsTextChangeNotification() const
{
return mIMENotificationRequests &&
mIMENotificationRequests->WantTextChange();
}
bool NeedsPositionChangeNotification() const
{
return mIMENotificationRequests &&
mIMENotificationRequests->WantPositionChanged();
}
void ClearPendingNotifications()
{
mNeedsToNotifyIMEOfFocusSet = false;
@ -326,7 +339,7 @@ private:
EventStateManager* mESM;
IMENotificationRequests mIMENotificationRequests;
const IMENotificationRequests* mIMENotificationRequests;
uint32_t mPreAttrChangeLength;
uint32_t mSuppressNotifications;
int64_t mPreCharacterDataChangeLength;

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

@ -22,6 +22,7 @@
#include "nsGenericHTMLElement.h"
#include "nsAttrValueInlines.h"
#include "nsPresContext.h"
#include "nsIClassOfService.h"
#include "nsIPresShell.h"
#include "nsGkAtoms.h"
#include "nsSize.h"
@ -1159,6 +1160,12 @@ public:
return;
}
nsCOMPtr<nsIClassOfService> cos;
if (aElement->mUseUrgentStartForChannel &&
(cos = do_QueryInterface(channel))) {
cos->AddClassFlags(nsIClassOfService::UrgentStart);
}
// The listener holds a strong reference to us. This creates a
// reference cycle, once we've set mChannel, which is manually broken
// in the listener's OnStartRequest method after it is finished with
@ -3891,6 +3898,14 @@ HTMLMediaElement::PlayInternal(ErrorResult& aRv)
// Play was not blocked so assume user interacted with the element.
mHasUserInteraction = true;
if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE) {
// The media load algorithm will be initiated by a user interaction.
// We want to boost the channel priority for better responsiveness.
// Note this must be done before UpdatePreloadAction() which will
// update |mPreloadAction|.
mUseUrgentStartForChannel = true;
}
StopSuspendingAfterFirstFrame();
SetPlayedOrSeeked(true);

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

@ -1624,6 +1624,10 @@ protected:
// True if a same-origin check has been done for the media element and resource.
bool mMediaSecurityVerified;
// True if we should set nsIClassOfService::UrgentStart to the channel to
// get the response ASAP for better user responsiveness.
bool mUseUrgentStartForChannel = false;
// The CORS mode when loading the media element
CORSMode mCORSMode;

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

@ -44,8 +44,143 @@ interface nsITextInputProcessorNotification : nsISupports
* "notify-blur" (optional)
* This is notified when an editable editor loses focus and Gecko stops
* observing the changes in the content.
*
* "notify-text-change" (optional)
* This is notified when text in the focused editor is modified.
* Some attributes below are available to retrieve the detail.
* IME shouldn't change DOM tree, focus nor something when this is notified.
* Note that when there is no chance to notify you of some text changes
* safely, this represents all changes as a change.
*
* "notify-selection-change" (optional)
* This is notified when selection in the focused editor is changed.
* Some attributes below are available to retrieve the detail.
* IME shouldn't change DOM tree, focus nor something when this is notified.
* Note that when there was no chance to notify you of this safely, this
* represents the latest selection change.
*
* "notify-position-change" (optional)
* This is notified when layout is changed in the editor or the window
* is moved.
* IME shouldn't change DOM tree, focus nor something when this is notified.
* Note that when there was no chance to notify you of this safely, this
* represents the latest layout change.
*/
readonly attribute ACString type;
/**
* Be careful, line breakers in the editor are treated as native line
* breakers. I.e., on Windows, a line breaker is "\r\n" (CRLF). On the
* others, it is "\n" (LF). If you want TextInputProcessor to treat line
* breakers on Windows as XP line breakers (LF), please file a bug with
* the reason why you need the behavior.
*/
/**
* This attribute has a valid value when type is "notify-text-change" or
* "notify-selection-change".
* This is offset of the start of modified text range if type is
* "notify-text-change". Or offset of start of selection if type is
* "notify-selection-change".
*/
readonly attribute unsigned long offset;
/**
* This attribute has a valid value when type is "notify-selection-change".
* This is selected text. I.e., the length is selected length and
* it's empty if the selection is collapsed.
*/
readonly attribute AString text;
/**
* This attribute has a valid value when type is "notify-selection-change".
* This is set to true when the selection is collapsed. Otherwise, false.
*/
readonly attribute boolean collapsed;
/**
* This attribute has a valid value when type is "notify-selection-change".
* This is selected length. I.e., if this is 0, collapsed is set to true.
*/
readonly attribute uint32_t length;
/**
* This attribute has a valid value when type is "notify-selection-change".
* When selection is created from latter point to former point, this is
* set to true. Otherwise, false.
* I.e., if this is true, offset + length is the anchor of selection.
*/
readonly attribute boolean reversed;
/**
* This attribute has a valid value when type is "notify-selection-change".
* This indicates the start of the selection's writing mode.
* The value can be "horizontal-tb", "vertical-rl" or "vertical-lr".
*/
readonly attribute ACString writingMode;
/**
* This attribute has a valid value when type is "notify-selection-change".
* If the selection change was caused by composition, this is set to true.
* Otherwise, false.
*/
readonly attribute boolean causedByComposition;
/**
* This attribute has a valid value when type is "notify-selection-change".
* If the selection change was caused by selection event, this is set to true.
* Otherwise, false.
*/
readonly attribute boolean causedBySelectionEvent;
/**
* This attribute has a valid value when type is "notify-selection-change".
* If the selection change occurred during composition, this is set to true.
* Otherwise, false.
*/
readonly attribute boolean occurredDuringComposition;
/**
* This attribute has a valid value when type is "notify-text-change".
* This is removed text length by the change(s). If this is empty, new text
* was just inserted. Otherwise, the text is replaced with new text.
*/
readonly attribute unsigned long removedLength;
/**
* This attribute has a valid value when type is "notify-text-change".
* This is added text length by the change(s). If this is empty, old text
* was just deleted. Otherwise, the text replaces the old text.
*/
readonly attribute unsigned long addedLength;
/**
* This attribute has a valid value when type is "notify-text-change".
* If the text change(s) was caused only by composition, this is set to true.
* Otherwise, false. I.e., if one of text changes are caused by JS or
* modifying without composition, this is set to false.
*/
readonly attribute boolean causedOnlyByComposition;
/**
* This attribute has a valid value when type is "notify-text-change".
* If at least one text change not caused by composition occurred during
* composition, this is set to true. Otherwise, false.
* Note that this is set to false when new change is caused by neither
* composition nor occurred during composition because it's outdated for
* new composition.
* In other words, when text changes not caused by composition occurred
* during composition and it may cause committing composition, this is
* set to true.
*/
readonly attribute boolean includingChangesDuringComposition;
/**
* This attribute has a valid value when type is "notify-text-change".
* If at least one text change occurred when there was no composition, this
* is set to true. Otherwise, false.
*/
readonly attribute boolean includingChangesWithoutComposition;
};
/**

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

@ -1742,7 +1742,7 @@ TabParent::RecvNotifyIMEFocus(const ContentCache& aContentCache,
IMEStateManager::NotifyIME(aIMENotification, widget, true);
if (aIMENotification.mMessage == NOTIFY_IME_OF_FOCUS) {
*aRequests = widget->GetIMENotificationRequests();
*aRequests = widget->IMENotificationRequestsRef();
}
return IPC_OK();
}
@ -1757,7 +1757,8 @@ TabParent::RecvNotifyIMETextChange(const ContentCache& aContentCache,
}
#ifdef DEBUG
IMENotificationRequests requests = widget->GetIMENotificationRequests();
const IMENotificationRequests& requests =
widget->IMENotificationRequestsRef();
NS_ASSERTION(requests.WantTextChange(),
"Don't call Send/RecvNotifyIMETextChange without NOTIFY_TEXT_CHANGE");
#endif

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

@ -299,6 +299,8 @@ InitAudioSpecificConfig(const Frame& frame,
} // namespace adts
using media::TimeUnit;
// ADTSDemuxer
ADTSDemuxer::ADTSDemuxer(MediaResource* aSource)
@ -384,7 +386,7 @@ ADTSTrackDemuxer::~ADTSTrackDemuxer()
bool
ADTSTrackDemuxer::Init()
{
FastSeek(media::TimeUnit());
FastSeek(TimeUnit::Zero());
// Read the first frame to fetch sample rate and other meta data.
RefPtr<MediaRawData> frame(GetNextFrame(FindNextFrame(true)));
@ -396,7 +398,7 @@ ADTSTrackDemuxer::Init()
}
// Rewind back to the stream begin to avoid dropping the first frame.
FastSeek(media::TimeUnit());
FastSeek(TimeUnit::Zero());
if (!mInfo) {
mInfo = MakeUnique<AudioInfo>();
@ -437,18 +439,18 @@ ADTSTrackDemuxer::GetInfo() const
}
RefPtr<ADTSTrackDemuxer::SeekPromise>
ADTSTrackDemuxer::Seek(const media::TimeUnit& aTime)
ADTSTrackDemuxer::Seek(const TimeUnit& aTime)
{
// Efficiently seek to the position.
FastSeek(aTime);
// Correct seek position by scanning the next frames.
const media::TimeUnit seekTime = ScanUntil(aTime);
const TimeUnit seekTime = ScanUntil(aTime);
return SeekPromise::CreateAndResolve(seekTime, __func__);
}
media::TimeUnit
ADTSTrackDemuxer::FastSeek(const media::TimeUnit& aTime)
TimeUnit
ADTSTrackDemuxer::FastSeek(const TimeUnit& aTime)
{
ADTSLOG("FastSeek(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
@ -480,8 +482,8 @@ ADTSTrackDemuxer::FastSeek(const media::TimeUnit& aTime)
return Duration(mFrameIndex);
}
media::TimeUnit
ADTSTrackDemuxer::ScanUntil(const media::TimeUnit& aTime)
TimeUnit
ADTSTrackDemuxer::ScanUntil(const TimeUnit& aTime)
{
ADTSLOG("ScanUntil(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
@ -557,12 +559,11 @@ ADTSTrackDemuxer::Reset()
if (mParser) {
mParser->Reset();
}
FastSeek(media::TimeUnit());
FastSeek(TimeUnit::Zero());
}
RefPtr<ADTSTrackDemuxer::SkipAccessPointPromise>
ADTSTrackDemuxer::SkipToNextRandomAccessPoint(
const media::TimeUnit& aTimeThreshold)
ADTSTrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold)
{
// Will not be called for audio-only resources.
return SkipAccessPointPromise::CreateAndReject(
@ -578,9 +579,9 @@ ADTSTrackDemuxer::GetResourceOffset() const
media::TimeIntervals
ADTSTrackDemuxer::GetBuffered()
{
media::TimeUnit duration = Duration();
auto duration = Duration();
if (duration <= media::TimeUnit()) {
if (!duration.IsPositive()) {
return media::TimeIntervals();
}
@ -594,28 +595,28 @@ ADTSTrackDemuxer::StreamLength() const
return mSource.GetLength();
}
media::TimeUnit
TimeUnit
ADTSTrackDemuxer::Duration() const
{
if (!mNumParsedFrames) {
return media::TimeUnit::FromMicroseconds(-1);
return TimeUnit::FromMicroseconds(-1);
}
const int64_t streamLen = StreamLength();
if (streamLen < 0) {
// Unknown length, we can't estimate duration.
return media::TimeUnit::FromMicroseconds(-1);
return TimeUnit::FromMicroseconds(-1);
}
const int64_t firstFrameOffset = mParser->FirstFrame().Offset();
int64_t numFrames = (streamLen - firstFrameOffset) / AverageFrameLength();
return Duration(numFrames);
}
media::TimeUnit
TimeUnit
ADTSTrackDemuxer::Duration(int64_t aNumFrames) const
{
if (!mSamplesPerSecond) {
return media::TimeUnit::FromMicroseconds(-1);
return TimeUnit::FromMicroseconds(-1);
}
return FramesToTimeUnit(aNumFrames * mSamplesPerFrame, mSamplesPerSecond);
@ -784,7 +785,7 @@ ADTSTrackDemuxer::FrameIndexFromOffset(int64_t aOffset) const
}
int64_t
ADTSTrackDemuxer::FrameIndexFromTime(const media::TimeUnit& aTime) const
ADTSTrackDemuxer::FrameIndexFromTime(const TimeUnit& aTime) const
{
int64_t frameIndex = 0;
if (mSamplesPerSecond > 0 && mSamplesPerFrame > 0) {

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

@ -1391,10 +1391,10 @@ MediaDecoder::GetSeekable()
return GetBuffered();
} else {
return media::TimeIntervals(
media::TimeInterval(media::TimeUnit::FromMicroseconds(0),
media::TimeInterval(TimeUnit::Zero(),
IsInfinite()
? media::TimeUnit::FromInfinity()
: media::TimeUnit::FromSeconds(GetDuration())));
? TimeUnit::FromInfinity()
: TimeUnit::FromSeconds(GetDuration())));
}
}
@ -1404,7 +1404,7 @@ MediaDecoder::SetFragmentEndTime(double aTime)
MOZ_ASSERT(NS_IsMainThread());
if (mDecoderStateMachine) {
mDecoderStateMachine->DispatchSetFragmentEndTime(
media::TimeUnit::FromSeconds(aTime));
TimeUnit::FromSeconds(aTime));
}
}
@ -1770,12 +1770,11 @@ MediaDecoder::NextFrameBufferedStatus()
MOZ_ASSERT(NS_IsMainThread());
// Next frame hasn't been decoded yet.
// Use the buffered range to consider if we have the next frame available.
media::TimeUnit currentPosition =
media::TimeUnit::FromMicroseconds(CurrentPosition());
TimeUnit currentPosition = TimeUnit::FromMicroseconds(CurrentPosition());
media::TimeInterval interval(
currentPosition,
currentPosition
+ media::TimeUnit::FromMicroseconds(DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED));
+ TimeUnit::FromMicroseconds(DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED));
return GetBuffered().Contains(interval)
? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
: MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;

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

@ -556,7 +556,7 @@ public:
{
return mDecoder->GetDescriptionName();
}
void SetSeekThreshold(const media::TimeUnit& aTime) override
void SetSeekThreshold(const TimeUnit& aTime) override
{
mDecoder->SetSeekThreshold(aTime);
}
@ -881,7 +881,7 @@ public:
return mInfo->Clone();
}
RefPtr<SeekPromise> Seek(const media::TimeUnit& aTime) override
RefPtr<SeekPromise> Seek(const TimeUnit& aTime) override
{
RefPtr<Wrapper> self = this;
return InvokeAsync(
@ -927,7 +927,7 @@ public:
}
RefPtr<SkipAccessPointPromise>
SkipToNextRandomAccessPoint(const media::TimeUnit& aTimeThreshold) override
SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold) override
{
RefPtr<Wrapper> self = this;
return InvokeAsync(
@ -1424,8 +1424,8 @@ MediaFormatReader::OnDemuxerInitDone(const MediaResult& aResult)
// For others, we must demux the first sample to know the start time for each
// track.
if (!mDemuxer->ShouldComputeStartTime()) {
mAudio.mFirstDemuxedSampleTime.emplace(TimeUnit::FromMicroseconds(0));
mVideo.mFirstDemuxedSampleTime.emplace(TimeUnit::FromMicroseconds(0));
mAudio.mFirstDemuxedSampleTime.emplace(TimeUnit::Zero());
mVideo.mFirstDemuxedSampleTime.emplace(TimeUnit::Zero());
} else {
if (HasAudio()) {
RequestDemuxSamples(TrackInfo::kAudioTrack);
@ -1512,10 +1512,10 @@ MediaFormatReader::GetDecoderData(TrackType aTrack)
bool
MediaFormatReader::ShouldSkip(bool aSkipToNextKeyframe,
media::TimeUnit aTimeThreshold)
TimeUnit aTimeThreshold)
{
MOZ_ASSERT(HasVideo());
media::TimeUnit nextKeyframe;
TimeUnit nextKeyframe;
nsresult rv = mVideo.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe);
if (NS_FAILED(rv)) {
return aSkipToNextKeyframe;
@ -1529,7 +1529,7 @@ MediaFormatReader::ShouldSkip(bool aSkipToNextKeyframe,
RefPtr<MediaDecoderReader::VideoDataPromise>
MediaFormatReader::RequestVideoData(bool aSkipToNextKeyframe,
const media::TimeUnit& aTimeThreshold)
const TimeUnit& aTimeThreshold)
{
MOZ_ASSERT(OnTaskQueue());
MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty(),
@ -2052,7 +2052,7 @@ MediaFormatReader::InternalSeek(TrackType aTrack,
RefPtr<MediaFormatReader> self = this;
decoder.mTrackDemuxer->Seek(decoder.mTimeThreshold.ref().Time())
->Then(OwnerThread(), __func__,
[self, aTrack] (media::TimeUnit aTime) {
[self, aTrack] (TimeUnit aTime) {
auto& decoder = self->GetDecoderData(aTrack);
decoder.mSeekRequest.Complete();
MOZ_ASSERT(
@ -2186,7 +2186,7 @@ MediaFormatReader::Update(TrackType aTrack)
while (decoder.mTimeThreshold && decoder.mOutput.Length()) {
RefPtr<MediaData>& output = decoder.mOutput[0];
InternalSeekTarget target = decoder.mTimeThreshold.ref();
media::TimeUnit time = output->mTime;
auto time = output->mTime;
if (time >= target.Time()) {
// We have reached our internal seek target.
decoder.mTimeThreshold.reset();
@ -2315,7 +2315,7 @@ MediaFormatReader::Update(TrackType aTrack)
decoder.mError.reset();
LOG("%s decoded error count %d", TrackTypeToStr(aTrack),
decoder.mNumOfConsecutiveError);
media::TimeUnit nextKeyframe;
TimeUnit nextKeyframe;
if (aTrack == TrackType::kVideoTrack && !decoder.HasInternalSeekPending()
&& NS_SUCCEEDED(
decoder.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe))) {
@ -2506,7 +2506,7 @@ MediaFormatReader::DropDecodedSamples(TrackType aTrack)
auto& decoder = GetDecoderData(aTrack);
size_t lengthDecodedQueue = decoder.mOutput.Length();
if (lengthDecodedQueue && decoder.mTimeThreshold.isSome()) {
TimeUnit time = decoder.mOutput.LastElement()->mTime;
auto time = decoder.mOutput.LastElement()->mTime;
if (time >= decoder.mTimeThreshold.ref().Time()) {
// We would have reached our internal seek target.
decoder.mTimeThreshold.reset();
@ -2520,7 +2520,7 @@ MediaFormatReader::DropDecodedSamples(TrackType aTrack)
}
void
MediaFormatReader::SkipVideoDemuxToNextKeyFrame(media::TimeUnit aTimeThreshold)
MediaFormatReader::SkipVideoDemuxToNextKeyFrame(TimeUnit aTimeThreshold)
{
MOZ_ASSERT(OnTaskQueue());
LOG("Skipping up to %" PRId64, aTimeThreshold.ToMicroseconds());
@ -2711,7 +2711,7 @@ MediaFormatReader::OnSeekFailed(TrackType aTrack, const MediaResult& aError)
// Ensure we have the most up to date buffered ranges.
UpdateReceivedNewData(TrackType::kAudioTrack);
Maybe<media::TimeUnit> nextSeekTime;
Maybe<TimeUnit> nextSeekTime;
// Find closest buffered time found after video seeked time.
for (const auto& timeRange : mAudio.mTimeRanges) {
if (timeRange.mStart >= mPendingSeekTime.ref()) {
@ -2745,7 +2745,7 @@ MediaFormatReader::DoVideoSeek()
{
MOZ_ASSERT(mPendingSeekTime.isSome());
LOGV("Seeking video to %" PRId64, mPendingSeekTime.ref().ToMicroseconds());
media::TimeUnit seekTime = mPendingSeekTime.ref();
auto seekTime = mPendingSeekTime.ref();
mVideo.mTrackDemuxer->Seek(seekTime)
->Then(OwnerThread(), __func__, this,
&MediaFormatReader::OnVideoSeekCompleted,
@ -2754,7 +2754,7 @@ MediaFormatReader::DoVideoSeek()
}
void
MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime)
MediaFormatReader::OnVideoSeekCompleted(TimeUnit aTime)
{
MOZ_ASSERT(OnTaskQueue());
LOGV("Video seeked to %" PRId64, aTime.ToMicroseconds());
@ -2828,7 +2828,7 @@ MediaFormatReader::DoAudioSeek()
{
MOZ_ASSERT(mPendingSeekTime.isSome());
LOGV("Seeking audio to %" PRId64, mPendingSeekTime.ref().ToMicroseconds());
media::TimeUnit seekTime = mPendingSeekTime.ref();
auto seekTime = mPendingSeekTime.ref();
mAudio.mTrackDemuxer->Seek(seekTime)
->Then(OwnerThread(), __func__, this,
&MediaFormatReader::OnAudioSeekCompleted,
@ -2837,7 +2837,7 @@ MediaFormatReader::DoAudioSeek()
}
void
MediaFormatReader::OnAudioSeekCompleted(media::TimeUnit aTime)
MediaFormatReader::OnAudioSeekCompleted(TimeUnit aTime)
{
MOZ_ASSERT(OnTaskQueue());
LOGV("Audio seeked to %" PRId64, aTime.ToMicroseconds());
@ -2939,7 +2939,7 @@ MediaFormatReader::UpdateBuffered()
if (HasVideo()) {
mVideo.mTimeRanges = mVideo.mTrackDemuxer->GetBuffered();
bool hasLastEnd;
media::TimeUnit lastEnd = mVideo.mTimeRanges.GetEnd(&hasLastEnd);
auto lastEnd = mVideo.mTimeRanges.GetEnd(&hasLastEnd);
if (hasLastEnd) {
if (mVideo.mLastTimeRangesEnd
&& mVideo.mLastTimeRangesEnd.ref() < lastEnd) {
@ -2953,7 +2953,7 @@ MediaFormatReader::UpdateBuffered()
if (HasAudio()) {
mAudio.mTimeRanges = mAudio.mTrackDemuxer->GetBuffered();
bool hasLastEnd;
media::TimeUnit lastEnd = mAudio.mTimeRanges.GetEnd(&hasLastEnd);
auto lastEnd = mAudio.mTimeRanges.GetEnd(&hasLastEnd);
if (hasLastEnd) {
if (mAudio.mLastTimeRangesEnd
&& mAudio.mLastTimeRangesEnd.ref() < lastEnd) {
@ -2975,12 +2975,12 @@ MediaFormatReader::UpdateBuffered()
}
if (!intervals.Length()
|| intervals.GetStart() == media::TimeUnit::FromMicroseconds(0)) {
|| intervals.GetStart() == TimeUnit::Zero()) {
// IntervalSet already starts at 0 or is empty, nothing to shift.
mBuffered = intervals;
} else {
mBuffered =
intervals.Shift(media::TimeUnit() - mInfo.mStartTime);
intervals.Shift(TimeUnit::Zero() - mInfo.mStartTime);
}
}

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

@ -39,51 +39,6 @@ namespace media {
// Number of nanoseconds per second. 1e9.
static const int64_t NSECS_PER_S = 1000000000;
struct Microseconds {
Microseconds()
: mValue(0)
{}
explicit Microseconds(int64_t aValue)
: mValue(aValue)
{}
double ToSeconds() {
return double(mValue) / USECS_PER_S;
}
static Microseconds FromSeconds(double aValue) {
MOZ_ASSERT(!IsNaN(aValue));
double val = aValue * USECS_PER_S;
if (val >= double(INT64_MAX)) {
return Microseconds(INT64_MAX);
} else if (val <= double(INT64_MIN)) {
return Microseconds(INT64_MIN);
} else {
return Microseconds(int64_t(val));
}
}
bool operator == (const Microseconds& aOther) const {
return mValue == aOther.mValue;
}
bool operator > (const Microseconds& aOther) const {
return mValue > aOther.mValue;
}
bool operator >= (const Microseconds& aOther) const {
return mValue >= aOther.mValue;
}
bool operator < (const Microseconds& aOther) const {
return mValue < aOther.mValue;
}
bool operator <= (const Microseconds& aOther) const {
return mValue <= aOther.mValue;
}
int64_t mValue;
};
// TimeUnit at present uses a CheckedInt64 as storage.
// INT64_MAX has the special meaning of being +oo.
class TimeUnit final {
@ -110,10 +65,6 @@ public:
return TimeUnit(aValue);
}
static TimeUnit FromMicroseconds(Microseconds aValue) {
return TimeUnit(aValue.mValue);
}
static constexpr TimeUnit FromNanoseconds(int64_t aValue) {
return TimeUnit(aValue / 1000);
}
@ -236,15 +187,6 @@ public:
: mValue(CheckedInt64(0))
{}
explicit TimeUnit(const Microseconds& aMicroseconds)
: mValue(aMicroseconds.mValue)
{}
TimeUnit& operator = (const Microseconds& aMicroseconds)
{
mValue = aMicroseconds.mValue;
return *this;
}
TimeUnit(const TimeUnit&) = default;
TimeUnit& operator = (const TimeUnit&) = default;

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

@ -34,6 +34,7 @@ NS_NAMED_LITERAL_CSTRING(kEMEKeySystemClearkey, "org.w3.clearkey");
NS_NAMED_LITERAL_CSTRING(kEMEKeySystemWidevine, "com.widevine.alpha");
using layers::PlanarYCbCrImage;
using media::TimeUnit;
CheckedInt64 SaferMultDiv(int64_t aValue, uint32_t aMul, uint32_t aDiv) {
int64_t major = aValue / aDiv;
@ -47,11 +48,11 @@ CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate) {
return SaferMultDiv(aFrames, USECS_PER_S, aRate);
}
media::TimeUnit FramesToTimeUnit(int64_t aFrames, uint32_t aRate) {
TimeUnit FramesToTimeUnit(int64_t aFrames, uint32_t aRate) {
int64_t major = aFrames / aRate;
int64_t remainder = aFrames % aRate;
return media::TimeUnit::FromMicroseconds(major) * USECS_PER_S +
(media::TimeUnit::FromMicroseconds(remainder) * USECS_PER_S) / aRate;
return TimeUnit::FromMicroseconds(major) * USECS_PER_S +
(TimeUnit::FromMicroseconds(remainder) * USECS_PER_S) / aRate;
}
// Converts from microseconds to number of audio frames, given the specified
@ -61,7 +62,7 @@ CheckedInt64 UsecsToFrames(int64_t aUsecs, uint32_t aRate) {
}
// Format TimeUnit as number of frames at given rate.
CheckedInt64 TimeUnitToFrames(const media::TimeUnit& aTime, uint32_t aRate) {
CheckedInt64 TimeUnitToFrames(const TimeUnit& aTime, uint32_t aRate) {
return UsecsToFrames(aTime.ToMicroseconds(), aRate);
}
@ -111,8 +112,8 @@ media::TimeIntervals GetEstimatedBufferedTimeRanges(mozilla::MediaResource* aStr
// Special case completely cached files. This also handles local files.
if (aStream->IsDataCachedToEndOfResource(0)) {
buffered +=
media::TimeInterval(media::TimeUnit::FromMicroseconds(0),
media::TimeUnit::FromMicroseconds(aDurationUsecs));
media::TimeInterval(TimeUnit::Zero(),
TimeUnit::FromMicroseconds(aDurationUsecs));
return buffered;
}
@ -135,9 +136,8 @@ media::TimeIntervals GetEstimatedBufferedTimeRanges(mozilla::MediaResource* aStr
int64_t endUs = BytesToTime(endOffset, totalBytes, aDurationUsecs);
if (startUs != endUs) {
buffered +=
media::TimeInterval(media::TimeUnit::FromMicroseconds(startUs),
media::TimeUnit::FromMicroseconds(endUs));
media::TimeInterval(TimeUnit::FromMicroseconds(startUs),
TimeUnit::FromMicroseconds(endUs));
}
startOffset = aStream->GetNextCachedData(endOffset);
}

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

@ -309,13 +309,13 @@ VideoTrackEncoder::AppendVideoSegment(const VideoSegment& aSegment)
nullDuration));
// Adapt to the time before the first frame. This extends the first frame
// from [start, end] to [0, end], but it'll do for now.
CheckedInt64 diff = FramesToUsecs(nullDuration, mTrackRate);
if (!diff.isValid()) {
auto diff = FramesToTimeUnit(nullDuration, mTrackRate);
if (!diff.IsValid()) {
NS_ERROR("null duration overflow");
return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
}
mLastChunk.mTimeStamp -= TimeDuration::FromMicroseconds(diff.value());
mLastChunk.mTimeStamp -= diff.ToTimeDuration();
mLastChunk.mDuration += nullDuration;
}

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

@ -572,8 +572,7 @@ MP4TrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
{
if (mNextKeyframeTime.isNothing()) {
// There's no next key frame.
*aTime =
media::TimeUnit::FromMicroseconds(std::numeric_limits<int64_t>::max());
*aTime = media::TimeUnit::FromInfinity();
} else {
*aTime = mNextKeyframeTime.value();
}

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

@ -54,11 +54,10 @@ TEST(IntervalSet, Constructors)
media::TimeInterval CreateTimeInterval(int32_t aStart, int32_t aEnd)
{
// Copy constructor test
media::Microseconds startus(aStart);
media::TimeUnit start(startus);
media::TimeUnit start = media::TimeUnit::FromMicroseconds(aStart);
media::TimeUnit end;
// operator= test
end = media::Microseconds(aEnd);
end = media::TimeUnit::FromMicroseconds(aEnd);
media::TimeInterval ti(start, end);
return ti;
}
@ -72,22 +71,24 @@ media::TimeIntervals CreateTimeIntervals(int32_t aStart, int32_t aEnd)
TEST(IntervalSet, TimeIntervalsConstructors)
{
const media::Microseconds start(1);
const media::Microseconds end(2);
const media::Microseconds fuzz;
const auto start = media::TimeUnit::FromMicroseconds(1);
const auto end = media::TimeUnit::FromMicroseconds(2);
const media::TimeUnit fuzz;
// Compiler exercise.
media::TimeInterval test1(start, end);
media::TimeInterval test2(test1);
media::TimeInterval test3(start, end, fuzz);
media::TimeInterval test4(test3);
media::TimeInterval test5 = CreateTimeInterval(start.mValue, end.mValue);
media::TimeInterval test5 =
CreateTimeInterval(start.ToMicroseconds(), end.ToMicroseconds());
media::TimeIntervals blah1(test1);
media::TimeIntervals blah2(blah1);
media::TimeIntervals blah3 = blah1 + test1;
media::TimeIntervals blah4 = test1 + blah1;
media::TimeIntervals blah5 = CreateTimeIntervals(start.mValue, end.mValue);
media::TimeIntervals blah5 =
CreateTimeIntervals(start.ToMicroseconds(), end.ToMicroseconds());
(void)test1; (void)test2; (void)test3; (void)test4; (void)test5;
(void)blah1; (void)blah2; (void)blah3; (void)blah4; (void)blah5;
@ -593,21 +594,15 @@ TEST(IntervalSet, TimeRangesMicroseconds)
{
media::TimeIntervals i0;
// Test media::Microseconds and TimeUnit interchangeability (compilation only)
media::TimeUnit time1{media::Microseconds(5)};
media::Microseconds microseconds(5);
media::TimeUnit time2 = media::TimeUnit(microseconds);
EXPECT_EQ(time1, time2);
i0 += media::TimeInterval(media::Microseconds(20), media::Microseconds(25));
i0 += media::TimeInterval(media::Microseconds(40), media::Microseconds(60));
i0 += media::TimeInterval(media::Microseconds(5), media::Microseconds(10));
i0 += media::TimeInterval(media::TimeUnit::FromMicroseconds(20), media::TimeUnit::FromMicroseconds(25));
i0 += media::TimeInterval(media::TimeUnit::FromMicroseconds(40), media::TimeUnit::FromMicroseconds(60));
i0 += media::TimeInterval(media::TimeUnit::FromMicroseconds(5), media::TimeUnit::FromMicroseconds(10));
media::TimeIntervals i1;
i1.Add(media::TimeInterval(media::Microseconds(16), media::Microseconds(27)));
i1.Add(media::TimeInterval(media::Microseconds(7), media::Microseconds(15)));
i1.Add(media::TimeInterval(media::Microseconds(53), media::Microseconds(57)));
i1.Add(media::TimeInterval(media::Microseconds(45), media::Microseconds(50)));
i1.Add(media::TimeInterval(media::TimeUnit::FromMicroseconds(16), media::TimeUnit::FromMicroseconds(27)));
i1.Add(media::TimeInterval(media::TimeUnit::FromMicroseconds(7), media::TimeUnit::FromMicroseconds(15)));
i1.Add(media::TimeInterval(media::TimeUnit::FromMicroseconds(53), media::TimeUnit::FromMicroseconds(57)));
i1.Add(media::TimeInterval(media::TimeUnit::FromMicroseconds(45), media::TimeUnit::FromMicroseconds(50)));
media::TimeIntervals i(i0 + i1);
RefPtr<dom::TimeRanges> tr = new dom::TimeRanges();

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

@ -437,14 +437,15 @@ TEST_F(MP3DemuxerTest, Duration) {
RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
ASSERT_TRUE(frameData);
const int64_t duration = target.mDemuxer->Duration().ToMicroseconds();
const int64_t pos = duration + 1e6;
const auto duration = target.mDemuxer->Duration();
const auto pos = duration + TimeUnit::FromMicroseconds(1e6);
// Attempt to seek 1 second past the end of stream.
target.mDemuxer->Seek(TimeUnit::FromMicroseconds(pos));
target.mDemuxer->Seek(pos);
// The seek should bring us to the end of the stream.
EXPECT_NEAR(duration, target.mDemuxer->SeekPosition().ToMicroseconds(),
target.mSeekError * duration);
EXPECT_NEAR(duration.ToMicroseconds(),
target.mDemuxer->SeekPosition().ToMicroseconds(),
target.mSeekError * duration.ToMicroseconds());
// Since we're at the end of the stream, there should be no frames left.
frameData = target.mDemuxer->DemuxSample();
@ -457,15 +458,16 @@ TEST_F(MP3DemuxerTest, Seek) {
RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
ASSERT_TRUE(frameData);
const int64_t seekTime = TimeUnit::FromSeconds(1).ToMicroseconds();
int64_t pos = target.mDemuxer->SeekPosition().ToMicroseconds();
const auto seekTime = TimeUnit::FromSeconds(1);
auto pos = target.mDemuxer->SeekPosition();
while (frameData) {
EXPECT_NEAR(pos, target.mDemuxer->SeekPosition().ToMicroseconds(),
target.mSeekError * pos);
EXPECT_NEAR(pos.ToMicroseconds(),
target.mDemuxer->SeekPosition().ToMicroseconds(),
target.mSeekError * pos.ToMicroseconds());
pos += seekTime;
target.mDemuxer->Seek(TimeUnit::FromMicroseconds(pos));
target.mDemuxer->Seek(pos);
frameData = target.mDemuxer->DemuxSample();
}
}
@ -476,16 +478,17 @@ TEST_F(MP3DemuxerTest, Seek) {
RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
ASSERT_TRUE(frameData);
const int64_t seekTime = TimeUnit::FromSeconds(1).ToMicroseconds();
int64_t pos = target.mDemuxer->SeekPosition().ToMicroseconds();
const auto seekTime = TimeUnit::FromSeconds(1);
auto pos = target.mDemuxer->SeekPosition();
while (frameData) {
EXPECT_NEAR(pos, target.mDemuxer->SeekPosition().ToMicroseconds(),
target.mSeekError * pos);
EXPECT_NEAR(pos.ToMicroseconds(),
target.mDemuxer->SeekPosition().ToMicroseconds(),
target.mSeekError * pos.ToMicroseconds());
pos += seekTime;
target.mDemuxer->Reset();
target.mDemuxer->Seek(TimeUnit::FromMicroseconds(pos));
target.mDemuxer->Seek(pos);
frameData = target.mDemuxer->DemuxSample();
}
}

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

@ -16,6 +16,7 @@
using namespace mozilla;
using namespace mp4_demuxer;
using media::TimeUnit;
class AutoTaskQueue;
@ -63,7 +64,7 @@ public:
RefPtr<MediaTrackDemuxer> track = aTrackDemuxer;
RefPtr<MP4DemuxerBinding> binding = this;
auto time = media::TimeUnit::Invalid();
auto time = TimeUnit::Invalid();
while (mIndex < mSamples.Length()) {
uint32_t i = mIndex++;
if (mSamples[i]->mKeyframe) {
@ -415,14 +416,14 @@ TEST(MP4Demuxer, GetNextKeyframe)
// gizmp-frag has two keyframes; one at dts=cts=0, and another at
// dts=cts=1000000. Verify we get expected results.
media::TimeUnit time;
TimeUnit time;
binding->mVideoTrack = binding->mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
binding->mVideoTrack->Reset();
binding->mVideoTrack->GetNextRandomAccessPoint(&time);
EXPECT_EQ(time.ToMicroseconds(), 0);
binding->mVideoTrack->GetSamples()->Then(binding->mTaskQueue, __func__,
[binding] () {
media::TimeUnit time;
TimeUnit time;
binding->mVideoTrack->GetNextRandomAccessPoint(&time);
EXPECT_EQ(time.ToMicroseconds(), 1000000);
binding->mTaskQueue->BeginShutdown();

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

@ -20,6 +20,7 @@ namespace mozilla {
namespace dom {
using base::Thread;
using media::TimeUnit;
using namespace ipc;
using namespace layers;
using namespace gfx;
@ -137,9 +138,9 @@ VideoDecoderParent::RecvInput(const MediaRawDataIPDL& aData)
return IPC_OK();
}
data->mOffset = aData.base().offset();
data->mTime = media::TimeUnit::FromMicroseconds(aData.base().time());
data->mTimecode = media::TimeUnit::FromMicroseconds(aData.base().timecode());
data->mDuration = media::TimeUnit::FromMicroseconds(aData.base().duration());
data->mTime = TimeUnit::FromMicroseconds(aData.base().time());
data->mTimecode = TimeUnit::FromMicroseconds(aData.base().timecode());
data->mDuration = TimeUnit::FromMicroseconds(aData.base().duration());
data->mKeyframe = aData.base().keyframe();
DeallocShmem(aData.buffer());
@ -257,7 +258,7 @@ VideoDecoderParent::RecvSetSeekThreshold(const int64_t& aTime)
{
MOZ_ASSERT(!mDestroyed);
MOZ_ASSERT(OnManagerThread());
mDecoder->SetSeekThreshold(media::TimeUnit::FromMicroseconds(aTime));
mDecoder->SetSeekThreshold(TimeUnit::FromMicroseconds(aTime));
return IPC_OK();
}

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

@ -22,12 +22,14 @@
namespace mozilla {
using media::TimeUnit;
/*
* A container class to make it easier to pass the playback info all the
* way to DecodedStreamGraphListener from DecodedStream.
*/
struct PlaybackInfoInit {
media::TimeUnit mStartTime;
TimeUnit mStartTime;
MediaInfo mInfo;
};
@ -144,8 +146,8 @@ public:
// mNextVideoTime is the end timestamp for the last packet sent to the stream.
// Therefore video packets starting at or after this time need to be copied
// to the output stream.
media::TimeUnit mNextVideoTime;
media::TimeUnit mNextAudioTime;
TimeUnit mNextVideoTime;
TimeUnit mNextAudioTime;
// The last video image sent to the stream. Useful if we need to replicate
// the image.
RefPtr<layers::Image> mLastVideoImage;
@ -294,13 +296,13 @@ DecodedStream::OnEnded(TrackType aType)
}
void
DecodedStream::Start(const media::TimeUnit& aStartTime, const MediaInfo& aInfo)
DecodedStream::Start(const TimeUnit& aStartTime, const MediaInfo& aInfo)
{
AssertOwnerThread();
MOZ_ASSERT(mStartTime.isNothing(), "playback already started.");
mStartTime.emplace(aStartTime);
mLastOutputTime = media::TimeUnit::Zero();
mLastOutputTime = TimeUnit::Zero();
mInfo = aInfo;
mPlaying = true;
ConnectListener();
@ -448,7 +450,7 @@ DecodedStream::SetPreservesPitch(bool aPreservesPitch)
}
static void
SendStreamAudio(DecodedStreamData* aStream, const media::TimeUnit& aStartTime,
SendStreamAudio(DecodedStreamData* aStream, const TimeUnit& aStartTime,
AudioData* aData, AudioSegment* aOutput, uint32_t aRate,
const PrincipalHandle& aPrincipalHandle)
{
@ -541,8 +543,8 @@ DecodedStream::SendAudio(double aVolume, bool aIsSameOrigin,
static void
WriteVideoToMediaStream(MediaStream* aStream,
layers::Image* aImage,
const media::TimeUnit& aEnd,
const media::TimeUnit& aStart,
const TimeUnit& aEnd,
const TimeUnit& aStart,
const mozilla::gfx::IntSize& aIntrinsicSize,
const TimeStamp& aTimeStamp,
VideoSegment* aOutput,
@ -711,7 +713,7 @@ DecodedStream::SendData()
}
}
media::TimeUnit
TimeUnit
DecodedStream::GetEndTime(TrackType aType) const
{
AssertOwnerThread();
@ -724,10 +726,10 @@ DecodedStream::GetEndTime(TrackType aType) const
} else if (aType == TrackInfo::kVideoTrack && mData) {
return mData->mNextVideoTime;
}
return media::TimeUnit::Zero();
return TimeUnit::Zero();
}
media::TimeUnit
TimeUnit
DecodedStream::GetPosition(TimeStamp* aTimeStamp) const
{
AssertOwnerThread();

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

@ -105,12 +105,11 @@ MediaSourceDecoder::GetSeekable()
}
if (buffered.Length()) {
seekable +=
media::TimeInterval(media::TimeUnit::FromSeconds(0), buffered.GetEnd());
seekable += media::TimeInterval(TimeUnit::Zero(), buffered.GetEnd());
}
} else {
seekable += media::TimeInterval(media::TimeUnit::FromSeconds(0),
media::TimeUnit::FromSeconds(duration));
seekable += media::TimeInterval(TimeUnit::Zero(),
TimeUnit::FromSeconds(duration));
}
MSE_DEBUG("ranges=%s", DumpTimeRanges(seekable).get());
return seekable;
@ -130,7 +129,7 @@ MediaSourceDecoder::GetBuffered()
// Media source object is shutting down.
return TimeIntervals();
}
media::TimeUnit highestEndTime;
TimeUnit highestEndTime;
nsTArray<media::TimeIntervals> activeRanges;
media::TimeIntervals buffered;
@ -144,8 +143,7 @@ MediaSourceDecoder::GetBuffered()
std::max(highestEndTime, activeRanges.LastElement().GetEnd());
}
buffered +=
media::TimeInterval(media::TimeUnit::FromMicroseconds(0), highestEndTime);
buffered += media::TimeInterval(TimeUnit::Zero(), highestEndTime);
for (auto& range : activeRanges) {
if (mEnded && range.Length()) {
@ -289,7 +287,7 @@ MediaSourceDecoder::NextFrameBufferedStatus()
TimeInterval interval(
currentPosition,
currentPosition
+ media::TimeUnit::FromMicroseconds(DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED));
+ TimeUnit::FromMicroseconds(DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED));
return buffered.ContainsStrict(ClampIntervalToEnd(interval))
? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
: MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;

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

@ -29,10 +29,7 @@ MediaSourceDemuxer::MediaSourceDemuxer(AbstractThread* aAbstractMainThread)
MOZ_ASSERT(NS_IsMainThread());
}
// Due to inaccuracies in determining buffer end
// frames (Bug 1065207). This value is based on videos seen in the wild.
const TimeUnit MediaSourceDemuxer::EOS_FUZZ =
media::TimeUnit::FromMicroseconds(500000);
constexpr TimeUnit MediaSourceDemuxer::EOS_FUZZ;
RefPtr<MediaSourceDemuxer::InitPromise>
MediaSourceDemuxer::Init()
@ -321,10 +318,10 @@ MediaSourceTrackDemuxer::GetInfo() const
}
RefPtr<MediaSourceTrackDemuxer::SeekPromise>
MediaSourceTrackDemuxer::Seek(const media::TimeUnit& aTime)
MediaSourceTrackDemuxer::Seek(const TimeUnit& aTime)
{
MOZ_ASSERT(mParent, "Called after BreackCycle()");
return InvokeAsync<media::TimeUnit&&>(
return InvokeAsync<TimeUnit&&>(
mParent->GetTaskQueue(), this, __func__,
&MediaSourceTrackDemuxer::DoSeek, aTime);
}
@ -346,7 +343,7 @@ MediaSourceTrackDemuxer::Reset()
NS_NewRunnableFunction([self] () {
self->mNextSample.reset();
self->mReset = true;
self->mManager->Seek(self->mType, TimeUnit(), TimeUnit());
self->mManager->Seek(self->mType, TimeUnit::Zero(), TimeUnit::Zero());
{
MonitorAutoLock mon(self->mMonitor);
self->mNextRandomAccessPoint = self->mManager->GetNextRandomAccessPoint(
@ -357,7 +354,7 @@ MediaSourceTrackDemuxer::Reset()
}
nsresult
MediaSourceTrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
MediaSourceTrackDemuxer::GetNextRandomAccessPoint(TimeUnit* aTime)
{
MonitorAutoLock mon(mMonitor);
*aTime = mNextRandomAccessPoint;
@ -366,9 +363,9 @@ MediaSourceTrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
RefPtr<MediaSourceTrackDemuxer::SkipAccessPointPromise>
MediaSourceTrackDemuxer::SkipToNextRandomAccessPoint(
const media::TimeUnit& aTimeThreshold)
const TimeUnit& aTimeThreshold)
{
return InvokeAsync<media::TimeUnit&&>(
return InvokeAsync<TimeUnit&&>(
mParent->GetTaskQueue(), this, __func__,
&MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint,
aTimeThreshold);
@ -393,20 +390,19 @@ MediaSourceTrackDemuxer::BreakCycles()
}
RefPtr<MediaSourceTrackDemuxer::SeekPromise>
MediaSourceTrackDemuxer::DoSeek(const media::TimeUnit& aTime)
MediaSourceTrackDemuxer::DoSeek(const TimeUnit& aTime)
{
TimeIntervals buffered = mManager->Buffered(mType);
// Fuzz factor represents a +/- threshold. So when seeking it allows the gap
// to be twice as big as the fuzz value. We only want to allow EOS_FUZZ gap.
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
TimeUnit seekTime = std::max(aTime - mPreRoll, TimeUnit::FromMicroseconds(0));
TimeUnit seekTime = std::max(aTime - mPreRoll, TimeUnit::Zero());
if (mManager->IsEnded() && seekTime >= buffered.GetEnd()) {
// We're attempting to seek past the end time. Cap seekTime so that we seek
// to the last sample instead.
seekTime =
std::max(mManager->HighestStartTime(mType) - mPreRoll,
TimeUnit::FromMicroseconds(0));
std::max(mManager->HighestStartTime(mType) - mPreRoll, TimeUnit::Zero());
}
if (!buffered.ContainsWithStrictEnd(seekTime)) {
if (!buffered.ContainsWithStrictEnd(aTime)) {
@ -427,7 +423,7 @@ MediaSourceTrackDemuxer::DoSeek(const media::TimeUnit& aTime)
MediaResult result = NS_OK;
RefPtr<MediaRawData> sample =
mManager->GetSample(mType,
media::TimeUnit(),
TimeUnit::Zero(),
result);
MOZ_ASSERT(NS_SUCCEEDED(result) && sample);
mNextSample = Some(sample);
@ -453,7 +449,7 @@ MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples)
return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM,
__func__);
}
if (!buffered.ContainsWithStrictEnd(TimeUnit::FromMicroseconds(0))) {
if (!buffered.ContainsWithStrictEnd(TimeUnit::Zero())) {
return SamplesPromise::CreateAndReject(
NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__);
}
@ -489,7 +485,7 @@ MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples)
RefPtr<MediaSourceTrackDemuxer::SkipAccessPointPromise>
MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint(
const media::TimeUnit& aTimeThreadshold)
const TimeUnit& aTimeThreadshold)
{
uint32_t parsed = 0;
// Ensure that the data we are about to skip to is still available.

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

@ -58,7 +58,10 @@ public:
void AddSizeOfResources(MediaSourceDecoder::ResourceSizes* aSizes);
// Gap allowed between frames.
static const media::TimeUnit EOS_FUZZ;
// Due to inaccuracies in determining buffer end
// frames (Bug 1065207). This value is based on videos seen in the wild.
static constexpr media::TimeUnit EOS_FUZZ =
media::TimeUnit::FromMicroseconds(500000);
private:
~MediaSourceDemuxer();

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

@ -36,6 +36,8 @@ namespace mozilla {
extern LazyLogModule gMediaDecoderLog;
#define LOG(type, msg) MOZ_LOG(gMediaDecoderLog, type, msg)
using media::TimeUnit;
/** Decoder base class for Ogg-encapsulated streams. */
OggCodecState*
OggCodecState::Create(ogg_page* aPage)
@ -259,9 +261,9 @@ OggCodecState::PacketOutAsMediaRawData()
int64_t duration = PacketDuration(packet.get());
NS_ASSERTION(duration >= 0, "duration invalid");
sample->mTimecode = media::TimeUnit::FromMicroseconds(packet->granulepos);
sample->mTime = media::TimeUnit::FromMicroseconds(end_tstamp - duration);
sample->mDuration = media::TimeUnit::FromMicroseconds(duration);
sample->mTimecode = TimeUnit::FromMicroseconds(packet->granulepos);
sample->mTime = TimeUnit::FromMicroseconds(end_tstamp - duration);
sample->mDuration = TimeUnit::FromMicroseconds(duration);
sample->mKeyframe = IsKeyframe(packet.get());
sample->mEOS = packet->e_o_s;

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

@ -188,7 +188,7 @@ OggDemuxer::HaveStartTime(TrackInfo::TrackType aType)
int64_t
OggDemuxer::StartTime(TrackInfo::TrackType aType)
{
return OggState(aType).mStartTime.refOr(TimeUnit::FromMicroseconds(0)).ToMicroseconds();
return OggState(aType).mStartTime.refOr(TimeUnit::Zero()).ToMicroseconds();
}
RefPtr<OggDemuxer::InitPromise>

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

@ -29,6 +29,8 @@ typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage;
namespace mozilla {
using media::TimeUnit;
/**
* FFmpeg calls back to this function with a list of pixel formats it supports.
* We choose a pixel format that we support and return it.
@ -342,7 +344,7 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample,
mImageContainer,
aSample->mOffset,
pts,
media::TimeUnit::FromMicroseconds(duration),
TimeUnit::FromMicroseconds(duration),
b,
!!mFrame->key_frame,
-1,
@ -364,7 +366,7 @@ RefPtr<MediaDataDecoder::DecodePromise>
FFmpegVideoDecoder<LIBAV_VER>::ProcessDrain()
{
RefPtr<MediaRawData> empty(new MediaRawData());
empty->mTimecode = media::TimeUnit::FromMicroseconds(mLastInputDts);
empty->mTimecode = TimeUnit::FromMicroseconds(mLastInputDts);
bool gotFrame = false;
DecodedData results;
while (NS_SUCCEEDED(DoDecode(empty, &gotFrame, results)) && gotFrame) {

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

@ -24,6 +24,8 @@ DEFINE_GUID(CLSID_CMSAACDecMFT, 0x32D186A7, 0x218F, 0x4C75, 0x88, 0x76, 0xDD, 0x
namespace mozilla {
using media::TimeUnit;
HRESULT
HNsToFrames(int64_t aHNs, uint32_t aRate, int64_t* aOutFrames)
{
@ -65,23 +67,23 @@ MFOffsetToInt32(const MFOffset& aOffset)
return int32_t(aOffset.value + (aOffset.fract / 65536.0f));
}
media::TimeUnit
TimeUnit
GetSampleDuration(IMFSample* aSample)
{
NS_ENSURE_TRUE(aSample, media::TimeUnit::Invalid());
NS_ENSURE_TRUE(aSample, TimeUnit::Invalid());
int64_t duration = 0;
aSample->GetSampleDuration(&duration);
return media::TimeUnit::FromMicroseconds(HNsToUsecs(duration));
return TimeUnit::FromMicroseconds(HNsToUsecs(duration));
}
media::TimeUnit
TimeUnit
GetSampleTime(IMFSample* aSample)
{
NS_ENSURE_TRUE(aSample, media::TimeUnit::Invalid());
NS_ENSURE_TRUE(aSample, TimeUnit::Invalid());
LONGLONG timestampHns = 0;
HRESULT hr = aSample->GetSampleTime(&timestampHns);
NS_ENSURE_TRUE(SUCCEEDED(hr), media::TimeUnit::Invalid());
return media::TimeUnit::FromMicroseconds(HNsToUsecs(timestampHns));
NS_ENSURE_TRUE(SUCCEEDED(hr), TimeUnit::Invalid());
return TimeUnit::FromMicroseconds(HNsToUsecs(timestampHns));
}
// Gets the sub-region of the video frame that should be displayed.

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

@ -41,6 +41,7 @@ using mozilla::layers::Image;
using mozilla::layers::IMFYCbCrImage;
using mozilla::layers::LayerManager;
using mozilla::layers::LayersBackend;
using mozilla::media::TimeUnit;
#if WINVER_MAXVER < 0x0A00
// Windows 10+ SDK has VP80 and VP90 defines
@ -826,9 +827,9 @@ WMFVideoMFTManager::CreateBasicVideoFrame(IMFSample* aSample,
b.mPlanes[2].mOffset = 0;
b.mPlanes[2].mSkip = 0;
media::TimeUnit pts = GetSampleTime(aSample);
TimeUnit pts = GetSampleTime(aSample);
NS_ENSURE_TRUE(pts.IsValid(), E_FAIL);
media::TimeUnit duration = GetSampleDuration(aSample);
TimeUnit duration = GetSampleDuration(aSample);
NS_ENSURE_TRUE(duration.IsValid(), E_FAIL);
nsIntRect pictureRegion = mVideoInfo.ScaledImageRect(videoWidth, videoHeight);
@ -897,9 +898,9 @@ WMFVideoMFTManager::CreateD3DVideoFrame(IMFSample* aSample,
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
NS_ENSURE_TRUE(image, E_FAIL);
media::TimeUnit pts = GetSampleTime(aSample);
TimeUnit pts = GetSampleTime(aSample);
NS_ENSURE_TRUE(pts.IsValid(), E_FAIL);
media::TimeUnit duration = GetSampleDuration(aSample);
TimeUnit duration = GetSampleDuration(aSample);
NS_ENSURE_TRUE(duration.IsValid(), E_FAIL);
RefPtr<VideoData> v = VideoData::CreateFromImage(mVideoInfo.mDisplay,
aStreamOffset,
@ -931,8 +932,8 @@ WMFVideoMFTManager::Output(int64_t aStreamOffset,
mDraining = false;
}
media::TimeUnit pts;
media::TimeUnit duration;
TimeUnit pts;
TimeUnit duration;
// Loop until we decode a sample, or an unexpected error that we can't
// handle occurs.
@ -993,14 +994,14 @@ WMFVideoMFTManager::Output(int64_t aStreamOffset,
if (!pts.IsValid() || !duration.IsValid()) {
return E_FAIL;
}
if (wasDraining && sampleCount == 1 && pts == media::TimeUnit()) {
if (wasDraining && sampleCount == 1 && pts == TimeUnit::Zero()) {
// WMF is unable to calculate a duration if only a single sample
// was parsed. Additionally, the pts always comes out at 0 under those
// circumstances.
// Seeing that we've only fed the decoder a single frame, the pts
// and duration are known, it's of the last sample.
pts = media::TimeUnit::FromMicroseconds(mLastTime);
duration = media::TimeUnit::FromMicroseconds(mLastDuration);
pts = TimeUnit::FromMicroseconds(mLastTime);
duration = TimeUnit::FromMicroseconds(mLastDuration);
}
if (mSeekTargetThreshold.isSome()) {
if ((pts + duration) < mSeekTargetThreshold.ref()) {

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

@ -189,12 +189,14 @@ skip-if = toolkit == 'android'
[test_peerConnection_restartIceLocalAndRemoteRollback.html]
skip-if = toolkit == 'android'
[test_peerConnection_scaleResolution.html]
skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator), Bug 1264343
[test_peerConnection_simulcastOffer.html]
skip-if = android_version # no simulcast support on android
[test_peerConnection_simulcastAnswer.html]
skip-if = android_version # no simulcast support on android
#[test_peerConnection_relayOnly.html] Bug 1222983
skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
# disable test_peerConnection_simulcastOffer.html for Bug 1351590
#[test_peerConnection_simulcastOffer.html]
#skip-if = android_version # no simulcast support on android
# disable test_peerConnection_simulcastAnswer.html for Bug 1351531
#[test_peerConnection_simulcastAnswer.html]
#skip-if = android_version # no simulcast support on android
#[test_peerConnection_relayOnly.html]
[test_peerConnection_callbacks.html]
skip-if = toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator)
[test_peerConnection_replaceTrack.html]

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

@ -39,6 +39,7 @@ extern mozilla::LazyLogModule gMediaDemuxerLog;
namespace mozilla {
using namespace gfx;
using media::TimeUnit;
LazyLogModule gNesteggLog("Nestegg");
@ -390,7 +391,7 @@ WebMDemuxer::ReadMetadata()
uint64_t duration = 0;
r = nestegg_duration(context, &duration);
if (!r) {
mInfo.mVideo.mDuration = media::TimeUnit::FromNanoseconds(duration);
mInfo.mVideo.mDuration = TimeUnit::FromNanoseconds(duration);
}
mInfo.mVideo.mCrypto = GetTrackCrypto(TrackInfo::kVideoTrack, track);
if (mInfo.mVideo.mCrypto.mValid) {
@ -413,8 +414,7 @@ WebMDemuxer::ReadMetadata()
mInfo.mAudio.mMimeType = "audio/opus";
OpusDataDecoder::AppendCodecDelay(
mInfo.mAudio.mCodecSpecificConfig,
media::TimeUnit::FromNanoseconds(params.codec_delay)
.ToMicroseconds());
TimeUnit::FromNanoseconds(params.codec_delay).ToMicroseconds());
}
mSeekPreroll = params.seek_preroll;
mInfo.mAudio.mRate = params.rate;
@ -457,7 +457,7 @@ WebMDemuxer::ReadMetadata()
uint64_t duration = 0;
r = nestegg_duration(context, &duration);
if (!r) {
mInfo.mAudio.mDuration = media::TimeUnit::FromNanoseconds(duration);
mInfo.mAudio.mDuration = TimeUnit::FromNanoseconds(duration);
}
mInfo.mAudio.mCrypto = GetTrackCrypto(TrackInfo::kAudioTrack, track);
if (mInfo.mAudio.mCrypto.mValid) {
@ -722,9 +722,9 @@ WebMDemuxer::GetNextPacket(TrackInfo::TrackType aType,
return NS_ERROR_OUT_OF_MEMORY;
}
}
sample->mTimecode = media::TimeUnit::FromMicroseconds(tstamp);
sample->mTime = media::TimeUnit::FromMicroseconds(tstamp);
sample->mDuration = media::TimeUnit::FromMicroseconds(next_tstamp - tstamp);
sample->mTimecode = TimeUnit::FromMicroseconds(tstamp);
sample->mTime = TimeUnit::FromMicroseconds(tstamp);
sample->mDuration = TimeUnit::FromMicroseconds(next_tstamp - tstamp);
sample->mOffset = holder->Offset();
sample->mKeyframe = isKeyframe;
if (discardPadding && i == count - 1) {
@ -737,7 +737,7 @@ WebMDemuxer::GetNextPacket(TrackInfo::TrackType aType,
WEBM_DEBUG("Invalid negative discard padding");
} else {
discardFrames = TimeUnitToFrames(
media::TimeUnit::FromNanoseconds(discardPadding), mInfo.mAudio.mRate);
TimeUnit::FromNanoseconds(discardPadding), mInfo.mAudio.mRate);
}
if (discardFrames.isValid()) {
sample->mDiscardPadding = discardFrames.value();
@ -926,7 +926,7 @@ WebMDemuxer::PushVideoPacket(NesteggPacketHolder* aItem)
nsresult
WebMDemuxer::SeekInternal(TrackInfo::TrackType aType,
const media::TimeUnit& aTarget)
const TimeUnit& aTarget)
{
EnsureUpToDateIndex();
uint32_t trackToSeek = mHasVideo ? mVideoTrack : mAudioTrack;
@ -942,21 +942,21 @@ WebMDemuxer::SeekInternal(TrackInfo::TrackType aType,
startTime = 0;
}
WEBM_DEBUG("Seek Target: %f",
media::TimeUnit::FromNanoseconds(target).ToSeconds());
TimeUnit::FromNanoseconds(target).ToSeconds());
if (target < mSeekPreroll || target - mSeekPreroll < startTime) {
target = startTime;
} else {
target -= mSeekPreroll;
}
WEBM_DEBUG("SeekPreroll: %f StartTime: %f Adjusted Target: %f",
media::TimeUnit::FromNanoseconds(mSeekPreroll).ToSeconds(),
media::TimeUnit::FromNanoseconds(startTime).ToSeconds(),
media::TimeUnit::FromNanoseconds(target).ToSeconds());
TimeUnit::FromNanoseconds(mSeekPreroll).ToSeconds(),
TimeUnit::FromNanoseconds(startTime).ToSeconds(),
TimeUnit::FromNanoseconds(target).ToSeconds());
}
int r = nestegg_track_seek(Context(aType), trackToSeek, target);
if (r == -1) {
WEBM_DEBUG("track_seek for track %u to %f failed, r=%d", trackToSeek,
media::TimeUnit::FromNanoseconds(target).ToSeconds(), r);
TimeUnit::FromNanoseconds(target).ToSeconds(), r);
// Try seeking directly based on cluster information in memory.
int64_t offset = 0;
bool rv = mBufferedState->GetOffsetForTime(target, &offset);
@ -1003,8 +1003,8 @@ WebMDemuxer::GetBuffered()
duration += startOffset;
}
WEBM_DEBUG("Duration: %f StartTime: %f",
media::TimeUnit::FromNanoseconds(duration).ToSeconds(),
media::TimeUnit::FromNanoseconds(startOffset).ToSeconds());
TimeUnit::FromNanoseconds(duration).ToSeconds(),
TimeUnit::FromNanoseconds(startOffset).ToSeconds());
}
for (uint32_t index = 0; index < ranges.Length(); index++) {
uint64_t start, end;
@ -1017,12 +1017,12 @@ WebMDemuxer::GetBuffered()
if (duration && end > duration) {
WEBM_DEBUG("limit range to duration, end: %f duration: %f",
media::TimeUnit::FromNanoseconds(end).ToSeconds(),
media::TimeUnit::FromNanoseconds(duration).ToSeconds());
TimeUnit::FromNanoseconds(end).ToSeconds(),
TimeUnit::FromNanoseconds(duration).ToSeconds());
end = duration;
}
media::TimeUnit startTime = media::TimeUnit::FromNanoseconds(start);
media::TimeUnit endTime = media::TimeUnit::FromNanoseconds(end);
auto startTime = TimeUnit::FromNanoseconds(start);
auto endTime = TimeUnit::FromNanoseconds(end);
WEBM_DEBUG("add range %f-%f", startTime.ToSeconds(), endTime.ToSeconds());
buffered += media::TimeInterval(startTime, endTime);
}
@ -1061,19 +1061,19 @@ WebMTrackDemuxer::GetInfo() const
}
RefPtr<WebMTrackDemuxer::SeekPromise>
WebMTrackDemuxer::Seek(const media::TimeUnit& aTime)
WebMTrackDemuxer::Seek(const TimeUnit& aTime)
{
// Seeks to aTime. Upon success, SeekPromise will be resolved with the
// actual time seeked to. Typically the random access point time
media::TimeUnit seekTime = aTime;
auto seekTime = aTime;
mSamples.Reset();
mParent->SeekInternal(mType, aTime);
nsresult rv = mParent->GetNextPacket(mType, &mSamples);
if (NS_FAILED(rv)) {
if (rv == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
// Ignore the error for now, the next GetSample will be rejected with EOS.
return SeekPromise::CreateAndResolve(media::TimeUnit(), __func__);
return SeekPromise::CreateAndResolve(TimeUnit::Zero(), __func__);
}
return SeekPromise::CreateAndReject(rv, __func__);
}
@ -1140,7 +1140,7 @@ WebMTrackDemuxer::SetNextKeyFrameTime()
return;
}
auto frameTime = media::TimeUnit::Invalid();
auto frameTime = TimeUnit::Invalid();
mNextKeyframeTime.reset();
@ -1226,12 +1226,11 @@ WebMTrackDemuxer::UpdateSamples(nsTArray<RefPtr<MediaRawData>>& aSamples)
}
nsresult
WebMTrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
WebMTrackDemuxer::GetNextRandomAccessPoint(TimeUnit* aTime)
{
if (mNextKeyframeTime.isNothing()) {
// There's no next key frame.
*aTime =
media::TimeUnit::FromMicroseconds(std::numeric_limits<int64_t>::max());
*aTime = TimeUnit::FromInfinity();
} else {
*aTime = mNextKeyframeTime.ref();
}
@ -1239,8 +1238,7 @@ WebMTrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
}
RefPtr<WebMTrackDemuxer::SkipAccessPointPromise>
WebMTrackDemuxer::SkipToNextRandomAccessPoint(
const media::TimeUnit& aTimeThreshold)
WebMTrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold)
{
uint32_t parsed = 0;
bool found = false;
@ -1282,7 +1280,7 @@ WebMTrackDemuxer::BreakCycles()
}
int64_t
WebMTrackDemuxer::GetEvictionOffset(const media::TimeUnit& aTime)
WebMTrackDemuxer::GetEvictionOffset(const TimeUnit& aTime)
{
int64_t offset;
if (!mParent->GetOffsetForTime(aTime.ToNanoseconds(), &offset)) {

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

@ -0,0 +1,38 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
var testGenerator = testSteps();
function* testSteps()
{
const invalidOrigin = {
url: "ftp://ftp.invalid.origin",
path: "storage/default/ftp+++ftp.invalid.origin"
};
info("Persisting an invalid origin");
let invalidPrincipal = getPrincipal(invalidOrigin.url);
let request = persist(invalidPrincipal, continueToNextStepSync);
yield undefined;
ok(request.resultCode === NS_ERROR_FAILURE,
"Persist() failed because of the invalid origin");
ok(request.result === null, "The request result is null");
let originDir = getRelativeFile(invalidOrigin.path);
let exists = originDir.exists();
ok(!exists, "Directory for invalid origin doesn't exist");
request = persisted(invalidPrincipal, continueToNextStepSync);
yield undefined;
ok(request.resultCode === NS_OK, "Persisted() succeeded");
ok(!request.result,
"The origin isn't persisted since the operation failed");
finishTest();
}

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

@ -7,23 +7,15 @@ var testGenerator = testSteps();
function* testSteps()
{
const origins = [
{
url: "http://default.test.persist",
path: "storage/default/http+++default.test.persist",
persistence: "default"
},
{
url: "ftp://ftp.invalid.origin",
path: "storage/default/ftp+++ftp.invalid.origin",
persistence: "default"
},
];
const origin = {
url: "http://default.test.persist",
path: "storage/default/http+++default.test.persist",
persistence: "default"
};
const metadataFileName = ".metadata-v2";
let principal = getPrincipal(origins[0].url);
let principal = getPrincipal(origin.url);
info("Persisting an uninitialized origin");
@ -42,7 +34,7 @@ function* testSteps()
ok(request.resultCode === NS_OK, "Persist() succeeded");
let originDir = getRelativeFile(origins[0].path);
let originDir = getRelativeFile(origin.path);
let exists = originDir.exists();
ok(exists, "Origin directory does exist");
@ -74,12 +66,12 @@ function* testSteps()
// Clear the origin since we'll test the same directory again under different
// circumstances.
clearOrigin(principal, origins[0].persistence, continueToNextStepSync);
clearOrigin(principal, origin.persistence, continueToNextStepSync);
yield undefined;
info("Persisting an already initialized origin");
initOrigin(principal, origins[0].persistence, continueToNextStepSync);
initOrigin(principal, origin.persistence, continueToNextStepSync);
yield undefined;
info("Reading out contents of metadata file");
@ -125,27 +117,5 @@ function* testSteps()
ok(request.resultCode === NS_OK, "Persisted() succeeded");
ok(request.result === originPersisted, "Persisted() concurs with metadata");
info("Persisting an invalid origin");
let invalidPrincipal = getPrincipal(origins[1].url);
request = persist(invalidPrincipal, continueToNextStepSync);
yield undefined;
ok(request.resultCode === NS_ERROR_FAILURE,
"Persist() failed because of the invalid origin");
ok(request.result === null, "The request result is null");
originDir = getRelativeFile(origins[1].path);
exists = originDir.exists();
ok(!exists, "Directory for invalid origin doesn't exist");
request = persisted(invalidPrincipal, continueToNextStepSync);
yield undefined;
ok(request.resultCode === NS_OK, "Persisted() succeeded");
ok(!request.result,
"The origin isn't persisted since the operation failed");
finishTest();
}

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

@ -18,6 +18,8 @@ support-files =
tempMetadataCleanup_profile.zip
[test_basics.js]
[test_bad_origin_directory.js]
skip-if = release_or_beta
[test_defaultStorageUpgrade.js]
[test_getUsage.js]
[test_idbSubdirUpgrade.js]

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

@ -6,7 +6,9 @@ support-files =
WebVRHelpers.js
[test_vrDisplay_exitPresent.html]
skip-if = (os != "win" && release_or_beta) # Enable Linux after Bug 1310655
[test_vrDisplay_getFrameData.html]
skip-if = (os != "win" && release_or_beta) # Enable Linux after Bug 1310655
[test_vrDisplay_onvrdisplaydeactivate_crosscontent.html]
skip-if = true
[test_vrDisplay_requestPresent.html]

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

@ -1,6 +1,5 @@
function runVRTest(callback) {
SpecialPowers.pushPrefEnv({"set" : [["dom.vr.enabled", true],
["dom.vr.puppet.enabled", true],
SpecialPowers.pushPrefEnv({"set" : [["dom.vr.puppet.enabled", true],
["dom.vr.require-gesture", false],
["dom.vr.test.enabled", true]]},
() => {

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

@ -349,6 +349,7 @@ private:
DECL_GFX_PREF(Live, "dom.meta-viewport.enabled", MetaViewportEnabled, bool, false);
DECL_GFX_PREF(Once, "dom.vr.enabled", VREnabled, bool, false);
DECL_GFX_PREF(Live, "dom.vr.autoactivate.enabled", VRAutoActivateEnabled, bool, false);
DECL_GFX_PREF(Live, "dom.vr.controller_trigger_threshold", VRControllerTriggerThreshold, float, 0.1f);
DECL_GFX_PREF(Live, "dom.vr.navigation.timeout", VRNavigationTimeout, int32_t, 1000);
DECL_GFX_PREF(Once, "dom.vr.oculus.enabled", VROculusEnabled, bool, true);
DECL_GFX_PREF(Once, "dom.vr.openvr.enabled", VROpenVREnabled, bool, false);

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

@ -1199,8 +1199,7 @@ VRSystemManagerOculus::HandleInput()
HandleButtonPress(i, buttonIdx, ovrButton_LThumb, inputState.Buttons,
inputState.Touches);
++buttonIdx;
HandleIndexTriggerPress(i, buttonIdx, ovrTouch_LIndexTrigger,
inputState.IndexTrigger[handIdx], inputState.Touches);
HandleIndexTriggerPress(i, buttonIdx, inputState.IndexTrigger[handIdx]);
++buttonIdx;
HandleHandTriggerPress(i, buttonIdx, inputState.HandTrigger[handIdx]);
++buttonIdx;
@ -1217,8 +1216,7 @@ VRSystemManagerOculus::HandleInput()
HandleButtonPress(i, buttonIdx, ovrButton_RThumb, inputState.Buttons,
inputState.Touches);
++buttonIdx;
HandleIndexTriggerPress(i, buttonIdx, ovrTouch_RIndexTrigger,
inputState.IndexTrigger[handIdx], inputState.Touches);
HandleIndexTriggerPress(i, buttonIdx, inputState.IndexTrigger[handIdx]);
++buttonIdx;
HandleHandTriggerPress(i, buttonIdx, inputState.HandTrigger[handIdx]);
++buttonIdx;
@ -1318,19 +1316,20 @@ VRSystemManagerOculus::HandleButtonPress(uint32_t aControllerIdx,
void
VRSystemManagerOculus::HandleIndexTriggerPress(uint32_t aControllerIdx,
uint32_t aButton,
uint64_t aTouchMask,
float aValue,
uint64_t aButtonTouched)
float aValue)
{
RefPtr<impl::VRControllerOculus> controller(mOculusController[aControllerIdx]);
MOZ_ASSERT(controller);
const uint64_t touchedDiff = (controller->GetButtonTouched() ^ aButtonTouched);
const float oldValue = controller->GetIndexTrigger();
// We prefer to let developers to set their own threshold for the adjustment.
// Therefore, we don't check ButtonPressed and ButtonTouched with TouchMask here.
// we just check the button value is larger than the threshold value or not.
const float threshold = gfxPrefs::VRControllerTriggerThreshold();
// Avoid sending duplicated events in IPC channels.
if ((oldValue != aValue) ||
(touchedDiff & aTouchMask)) {
NewButtonEvent(aControllerIdx, aButton, aValue > 0.1f, aTouchMask & aButtonTouched, aValue);
if (oldValue != aValue) {
NewButtonEvent(aControllerIdx, aButton, aValue > threshold,
aValue > threshold, aValue);
controller->SetIndexTrigger(aValue);
}
}
@ -1343,10 +1342,15 @@ VRSystemManagerOculus::HandleHandTriggerPress(uint32_t aControllerIdx,
RefPtr<impl::VRControllerOculus> controller(mOculusController[aControllerIdx]);
MOZ_ASSERT(controller);
const float oldValue = controller->GetHandTrigger();
// We prefer to let developers to set their own threshold for the adjustment.
// Therefore, we don't check ButtonPressed and ButtonTouched with TouchMask here.
// we just check the button value is larger than the threshold value or not.
const float threshold = gfxPrefs::VRControllerTriggerThreshold();
// Avoid sending duplicated events in IPC channels.
if (oldValue != aValue) {
NewButtonEvent(aControllerIdx, aButton, aValue > 0.1f, aValue > 0.1f, aValue);
NewButtonEvent(aControllerIdx, aButton, aValue > threshold,
aValue > threshold, aValue);
controller->SetHandTrigger(aValue);
}
}

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

@ -169,10 +169,8 @@ private:
void HandlePoseTracking(uint32_t aControllerIdx,
const dom::GamepadPoseState& aPose,
VRControllerHost* aController);
void HandleIndexTriggerPress(uint32_t aControllerIdx, uint32_t aButton,
uint64_t aTouchMask, float aValue, uint64_t aButtonTouched);
void HandleHandTriggerPress(uint32_t aControllerIdx, uint32_t aButton,
float aValue);
void HandleIndexTriggerPress(uint32_t aControllerIdx, uint32_t aButton, float aValue);
void HandleHandTriggerPress(uint32_t aControllerIdx, uint32_t aButton, float aValue);
void HandleTouchEvent(uint32_t aControllerIdx, uint32_t aButton,
uint64_t aTouchMask, uint64_t aTouched);
PRLibrary* mOvrLib;

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

@ -647,11 +647,8 @@ VRSystemManagerOpenVR::HandleInput()
state.ulButtonPressed, state.ulButtonTouched);
++buttonIdx;
break;
case ::vr::EVRControllerAxisType::k_eControllerAxis_Trigger:
HandleTriggerPress(i, buttonIdx,
::vr::ButtonMaskFromId(
static_cast<::vr::EVRButtonId>(::vr::k_EButton_Axis0 + j)),
state.rAxis[j].x, state.ulButtonPressed, state.ulButtonTouched);
case vr::EVRControllerAxisType::k_eControllerAxis_Trigger:
HandleTriggerPress(i, buttonIdx, state.rAxis[j].x);
++buttonIdx;
break;
}
@ -786,23 +783,21 @@ VRSystemManagerOpenVR::HandleButtonPress(uint32_t aControllerIdx,
void
VRSystemManagerOpenVR::HandleTriggerPress(uint32_t aControllerIdx,
uint32_t aButton,
uint64_t aButtonMask,
float aValue,
uint64_t aButtonPressed,
uint64_t aButtonTouched)
float aValue)
{
RefPtr<impl::VRControllerOpenVR> controller(mOpenVRController[aControllerIdx]);
MOZ_ASSERT(controller);
const uint64_t pressedDiff = (controller->GetButtonPressed() ^ aButtonPressed);
const uint64_t touchedDiff = (controller->GetButtonTouched() ^ aButtonTouched);
const float oldValue = controller->GetTrigger();
// For OpenVR, the threshold value of ButtonPressed and ButtonTouched is 0.55.
// We prefer to let developers to set their own threshold for the adjustment.
// Therefore, we don't check ButtonPressed and ButtonTouched with ButtonMask here.
// we just check the button value is larger than the threshold value or not.
const float threshold = gfxPrefs::VRControllerTriggerThreshold();
// Avoid sending duplicated events in IPC channels.
if ((oldValue != aValue) ||
(pressedDiff & aButtonMask) ||
(touchedDiff & aButtonMask)) {
NewButtonEvent(aControllerIdx, aButton, aButtonMask & aButtonPressed,
aButtonMask & aButtonTouched, aValue);
if (oldValue != aValue) {
NewButtonEvent(aControllerIdx, aButton, aValue > threshold,
aValue > threshold, aValue);
controller->SetTrigger(aValue);
}
}

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

@ -132,10 +132,7 @@ private:
uint64_t aButtonTouched);
void HandleTriggerPress(uint32_t aControllerIdx,
uint32_t aButton,
uint64_t aButtonMask,
float aValue,
uint64_t aButtonPressed,
uint64_t aButtonTouched);
float aValue);
void HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
float aValue);
void HandlePoseTracking(uint32_t aControllerIdx,

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

@ -78,7 +78,6 @@ LOCAL_INCLUDES += [
]
RESOURCE_FILES += [
'langGroups.properties',
'language.properties',
]

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

@ -207,6 +207,10 @@ function treatAsSafeArgument(entry, varName, csuName)
["Gecko_ClearWillChange", "aDisplay", null],
["Gecko_AppendWillChange", "aDisplay", null],
["Gecko_CopyWillChangeFrom", "aDest", null],
["Gecko_InitializeImageCropRect", "aImage", null],
["Gecko_CopyShapeSourceFrom", "aDst", null],
["Gecko_DestroyShapeSource", "aShape", null],
["Gecko_StyleShapeSource_SetURLValue", "aShape", null],
];
for (var [entryMatch, varMatch, csuMatch] of whitelist) {
assert(entryMatch || varMatch || csuMatch);

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

@ -48,7 +48,7 @@ fails == transform-floating-point-invalidation.html transform-floating-point-inv
fails == transform-floating-point-invalidation.html?reverse transform-floating-point-invalidation.html?reverse
fails == nudge-to-integer-invalidation.html nudge-to-integer-invalidation.html
fails == nudge-to-integer-invalidation.html?reverse nudge-to-integer-invalidation.html?reverse
== clipped-animated-transform-1.html clipped-animated-transform-1.html
skip-if(stylo) == clipped-animated-transform-1.html clipped-animated-transform-1.html # bug 1334036
fails == paintedlayer-recycling-1.html paintedlayer-recycling-1.html
fails == paintedlayer-recycling-2.html paintedlayer-recycling-2.html
fails pref(layers.single-tile.enabled,false) == paintedlayer-recycling-3.html paintedlayer-recycling-3.html

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

@ -650,7 +650,7 @@ Gecko_MatchStringArgPseudo(RawGeckoElementBorrowed aElement,
EventStates dummyMask; // mask is never read because we pass aDependence=nullptr
return nsCSSRuleProcessor::StringPseudoMatches(aElement, aType, aIdent,
aElement->OwnerDoc(), true,
dummyMask, false, aSetSlowSelectorFlag, nullptr);
dummyMask, aSetSlowSelectorFlag, nullptr);
}
nsIAtom*
@ -1809,6 +1809,12 @@ void
Gecko_CSSFontFaceRule_GetCssText(const nsCSSFontFaceRule* aRule,
nsAString* aResult)
{
// GetCSSText serializes nsCSSValues, which have a heap write
// hazard when dealing with color values (nsCSSKeywords::AddRefTable)
// We only serialize on the main thread; assert to convince the analysis
// and prevent accidentally calling this elsewhere
MOZ_ASSERT(NS_IsMainThread());
aRule->GetCssText(*aResult);
}

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

@ -16,6 +16,7 @@
#include "mozilla/dom/KeyframeEffectReadOnly.h"
#include "nsCSSAnonBoxes.h"
#include "nsCSSPseudoElements.h"
#include "nsCSSRuleProcessor.h"
#include "nsDeviceContext.h"
#include "nsHTMLStyleSheet.h"
#include "nsIDocumentInlines.h"
@ -245,6 +246,8 @@ ServoStyleSet::PreTraverseSync()
{
ResolveMappedAttrDeclarationBlocks();
nsCSSRuleProcessor::InitSystemMetrics();
// This is lazily computed and pseudo matching needs to access
// it so force computation early.
mPresContext->Document()->GetDocumentState();

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

@ -208,6 +208,10 @@ CSS_STATE_PSEUDO_CLASS(mozMathIncrementScriptLevel,
":-moz-math-increment-script-level", 0, "",
NS_EVENT_STATE_INCREMENT_SCRIPT_LEVEL)
CSS_STATE_PSEUDO_CLASS(mozAutofill, ":-moz-autofill",
CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME, "",
NS_EVENT_STATE_AUTOFILL)
// CSS 3 UI
// http://www.w3.org/TR/2004/CR-css3-ui-20040511/#pseudo-classes
CSS_STATE_PSEUDO_CLASS(required, ":required", 0, "", NS_EVENT_STATE_REQUIRED)

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

@ -47,6 +47,7 @@
#include "mozilla/ArrayUtils.h"
#include "mozilla/EventStates.h"
#include "mozilla/Preferences.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/Likely.h"
#include "mozilla/OperatorNewExtensions.h"
@ -1061,13 +1062,15 @@ nsCSSRuleProcessor::Startup()
true);
}
static bool
InitSystemMetrics()
/* static */ void
nsCSSRuleProcessor::InitSystemMetrics()
{
NS_ASSERTION(!sSystemMetrics, "already initialized");
if (sSystemMetrics)
return;
MOZ_ASSERT(NS_IsMainThread());
sSystemMetrics = new nsTArray< nsCOMPtr<nsIAtom> >;
NS_ENSURE_TRUE(sSystemMetrics, false);
/***************************************************************************
* ANY METRICS ADDED HERE SHOULD ALSO BE ADDED AS MEDIA QUERIES IN *
@ -1193,8 +1196,6 @@ InitSystemMetrics()
}
}
#endif
return true;
}
/* static */ void
@ -1213,9 +1214,7 @@ nsCSSRuleProcessor::Shutdown()
/* static */ bool
nsCSSRuleProcessor::HasSystemMetric(nsIAtom* aMetric)
{
if (!sSystemMetrics && !InitSystemMetrics()) {
return false;
}
nsCSSRuleProcessor::InitSystemMetrics();
return sSystemMetrics->IndexOf(aMetric) != sSystemMetrics->NoIndex;
}
@ -1223,8 +1222,7 @@ nsCSSRuleProcessor::HasSystemMetric(nsIAtom* aMetric)
/* static */ uint8_t
nsCSSRuleProcessor::GetWindowsThemeIdentifier()
{
if (!sSystemMetrics)
InitSystemMetrics();
nsCSSRuleProcessor::InitSystemMetrics();
return sWinThemeId;
}
#endif
@ -1639,17 +1637,16 @@ StateSelectorMatches(Element* aElement,
static inline bool
IsSignificantChildMaybeThreadSafe(const nsIContent* aContent,
bool aTextIsSignificant,
bool aWhitespaceIsSignificant,
bool aIsGecko)
bool aWhitespaceIsSignificant)
{
if (aIsGecko) {
auto content = const_cast<nsIContent*>(aContent);
return IsSignificantChild(content, aTextIsSignificant, aWhitespaceIsSignificant);
} else {
if (ServoStyleSet::IsInServoTraversal()) {
// See bug 1349100 for optimizing this
return nsStyleUtil::ThreadSafeIsSignificantChild(aContent,
aTextIsSignificant,
aWhitespaceIsSignificant);
} else {
auto content = const_cast<nsIContent*>(aContent);
return IsSignificantChild(content, aTextIsSignificant, aWhitespaceIsSignificant);
}
}
@ -1660,7 +1657,6 @@ nsCSSRuleProcessor::StringPseudoMatches(const mozilla::dom::Element* aElement,
const nsIDocument* aDocument,
bool aForStyling,
EventStates aStateMask,
bool aIsGecko,
bool* aSetSlowSelectorFlag,
bool* const aDependence)
{
@ -1670,13 +1666,13 @@ nsCSSRuleProcessor::StringPseudoMatches(const mozilla::dom::Element* aElement,
case CSSPseudoClassType::mozLocaleDir:
{
bool docIsRTL;
if (aIsGecko) {
if (ServoStyleSet::IsInServoTraversal()) {
docIsRTL = aDocument->ThreadSafeGetDocumentState()
.HasState(NS_DOCUMENT_STATE_RTL_LOCALE);
} else {
auto doc = const_cast<nsIDocument*>(aDocument);
docIsRTL = doc->GetDocumentState()
.HasState(NS_DOCUMENT_STATE_RTL_LOCALE);
} else {
docIsRTL = aDocument->ThreadSafeGetDocumentState()
.HasState(NS_DOCUMENT_STATE_RTL_LOCALE);
}
nsDependentString dirString(aString);
@ -1722,7 +1718,7 @@ nsCSSRuleProcessor::StringPseudoMatches(const mozilla::dom::Element* aElement,
do {
child = aElement->GetChildAt(++index);
} while (child &&
(!IsSignificantChildMaybeThreadSafe(child, true, false, aIsGecko) ||
(!IsSignificantChildMaybeThreadSafe(child, true, false) ||
(child->GetNameSpaceID() == aElement->GetNameSpaceID() &&
child->NodeInfo()->NameAtom()->Equals(nsDependentString(aString)))));
if (child) {
@ -2168,7 +2164,6 @@ static bool SelectorMatches(Element* aElement,
aTreeMatchContext.mDocument,
aTreeMatchContext.mForStyling,
aNodeMatchContext.mStateMask,
true,
&setSlowSelectorFlag,
aDependence);
if (setSlowSelectorFlag) {

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

@ -82,6 +82,7 @@ public:
nsresult ClearRuleCascades();
static void Startup();
static void InitSystemMetrics();
static void Shutdown();
static void FreeSystemMetrics();
static bool HasSystemMetric(nsIAtom* aMetric);
@ -152,7 +153,6 @@ public:
* (For setting the slow selector flag)
* @param aStateMask Mask containing states which we should exclude.
* Ignored if aDependence is null
* @param aIsGecko Set if Gecko.
* @param aSetSlowSelectorFlag Outparam, set if the caller is
* supposed to set the slow selector flag.
* @param aDependence Pointer to be set to true if we ignored a state due to
@ -164,7 +164,6 @@ public:
const nsIDocument* aDocument,
bool aForStyling,
mozilla::EventStates aStateMask,
bool aIsGecko,
bool* aSetSlowSelectorFlag,
bool* const aDependence = nullptr);

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

@ -1235,3 +1235,7 @@ input[type="number"] > div > div > div:hover {
/* give some indication of hover state for the up/down buttons */
background-color: lightblue;
}
:-moz-autofill {
filter: grayscale(21%) brightness(88%) contrast(161%) invert(10%) sepia(40%) saturate(206%);
}

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

@ -395,38 +395,38 @@ GetDifferentField(const mozilla::TrackInfo& info,
const mozilla::TrackInfo& infoRust)
{
if (infoRust.mId != info.mId) { return "Id"; }
if (infoRust.mKind == info.mKind) { return "Kind"; }
if (infoRust.mLabel == info.mLabel) { return "Label"; }
if (infoRust.mLanguage == info.mLanguage) { return "Language"; }
if (infoRust.mEnabled == info.mEnabled) { return "Enabled"; }
if (infoRust.mTrackId == info.mTrackId) { return "TrackId"; }
if (infoRust.mMimeType == info.mMimeType) { return "MimeType"; }
if (infoRust.mDuration == info.mDuration) { return "Duration"; }
if (infoRust.mMediaTime == info.mMediaTime) { return "MediaTime"; }
if (infoRust.mCrypto.mValid == info.mCrypto.mValid) { return "Crypto-Valid"; }
if (infoRust.mCrypto.mMode == info.mCrypto.mMode) { return "Crypto-Mode"; }
if (infoRust.mCrypto.mIVSize == info.mCrypto.mIVSize) { return "Crypto-IVSize"; }
if (infoRust.mCrypto.mKeyId == info.mCrypto.mKeyId) { return "Crypto-KeyId"; }
if (infoRust.mKind != info.mKind) { return "Kind"; }
if (infoRust.mLabel != info.mLabel) { return "Label"; }
if (infoRust.mLanguage != info.mLanguage) { return "Language"; }
if (infoRust.mEnabled != info.mEnabled) { return "Enabled"; }
if (infoRust.mTrackId != info.mTrackId) { return "TrackId"; }
if (infoRust.mMimeType != info.mMimeType) { return "MimeType"; }
if (infoRust.mDuration != info.mDuration) { return "Duration"; }
if (infoRust.mMediaTime != info.mMediaTime) { return "MediaTime"; }
if (infoRust.mCrypto.mValid != info.mCrypto.mValid) { return "Crypto-Valid"; }
if (infoRust.mCrypto.mMode != info.mCrypto.mMode) { return "Crypto-Mode"; }
if (infoRust.mCrypto.mIVSize != info.mCrypto.mIVSize) { return "Crypto-IVSize"; }
if (infoRust.mCrypto.mKeyId != info.mCrypto.mKeyId) { return "Crypto-KeyId"; }
switch (info.GetType()) {
case mozilla::TrackInfo::kAudioTrack: {
const AudioInfo *audioRust = infoRust.GetAsAudioInfo();
const AudioInfo *audio = info.GetAsAudioInfo();
if (audioRust->mRate == audio->mRate) { return "Rate"; }
if (audioRust->mChannels == audio->mChannels) { return "Channels"; }
if (audioRust->mBitDepth == audio->mBitDepth) { return "BitDepth"; }
if (audioRust->mProfile == audio->mProfile) { return "Profile"; }
if (audioRust->mExtendedProfile == audio->mExtendedProfile) { return "ExtendedProfile"; }
if (audioRust->mRate != audio->mRate) { return "Rate"; }
if (audioRust->mChannels != audio->mChannels) { return "Channels"; }
if (audioRust->mBitDepth != audio->mBitDepth) { return "BitDepth"; }
if (audioRust->mProfile != audio->mProfile) { return "Profile"; }
if (audioRust->mExtendedProfile != audio->mExtendedProfile) { return "ExtendedProfile"; }
break;
}
case mozilla::TrackInfo::kVideoTrack: {
const VideoInfo *videoRust = infoRust.GetAsVideoInfo();
const VideoInfo *video = info.GetAsVideoInfo();
if (videoRust->mDisplay == video->mDisplay) { return "Display"; }
if (videoRust->mImage == video->mImage) { return "Image"; }
if (*videoRust->mExtraData == *video->mExtraData) { return "ExtraData"; }
if (videoRust->mDisplay != video->mDisplay) { return "Display"; }
if (videoRust->mImage != video->mImage) { return "Image"; }
if (*videoRust->mExtraData != *video->mExtraData) { return "ExtraData"; }
// mCodecSpecificConfig is for video/mp4-es, not video/avc. Since video/mp4-es
// is supported on b2g only, it could be removed from TrackInfo.
if (*videoRust->mCodecSpecificConfig == *video->mCodecSpecificConfig) { return "CodecSpecificConfig"; }
if (*videoRust->mCodecSpecificConfig != *video->mCodecSpecificConfig) { return "CodecSpecificConfig"; }
break;
}
default:

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

@ -264,7 +264,7 @@
#include ../search/manifests/SearchAndroidManifest_activities.xml.in
#endif
#if MOZ_CRASHREPORTER
#ifdef MOZ_CRASHREPORTER
<activity android:name="org.mozilla.gecko.CrashReporter"
android:process="@ANDROID_PACKAGE_NAME@.CrashReporter"
android:label="@string/crash_reporter_title"

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

@ -191,7 +191,7 @@ public class AppConstants {
//#endif
public static final boolean MOZ_CRASHREPORTER =
//#if MOZ_CRASHREPORTER
//#ifdef MOZ_CRASHREPORTER
true;
//#else
false;

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

@ -8,28 +8,6 @@ ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
.NOTPARALLEL:
endif
MOZ_BUILDID := $(shell awk '{print $$3}' $(DEPTH)/buildid.h)
# Set the appropriate version code, based on the existance of the
# MOZ_APP_ANDROID_VERSION_CODE variable.
ifdef MOZ_APP_ANDROID_VERSION_CODE
ANDROID_VERSION_CODE:=$(MOZ_APP_ANDROID_VERSION_CODE)
else
ANDROID_VERSION_CODE:=$(shell $(PYTHON) \
$(topsrcdir)/python/mozbuild/mozbuild/android_version_code.py \
--verbose \
--with-android-cpu-arch=$(ANDROID_CPU_ARCH) \
$(if $(MOZ_ANDROID_MIN_SDK_VERSION),--with-android-min-sdk=$(MOZ_ANDROID_MIN_SDK_VERSION)) \
$(if $(MOZ_ANDROID_MAX_SDK_VERSION),--with-android-max-sdk=$(MOZ_ANDROID_MAX_SDK_VERSION)) \
$(MOZ_BUILDID))
endif
DEFINES += \
-DANDROID_VERSION_CODE=$(ANDROID_VERSION_CODE) \
-DMOZ_ANDROID_SHARED_ID="$(MOZ_ANDROID_SHARED_ID)" \
-DMOZ_BUILDID=$(MOZ_BUILDID) \
$(NULL)
GARBAGE += \
classes.dex \
gecko.ap_ \

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

@ -28,6 +28,7 @@ import sys
import buildconfig
from mozbuild import preprocessor
from mozbuild.android_version_code import android_version_code
def _defines():
@ -45,6 +46,7 @@ def _defines():
'MOZ_ANDROID_GCM',
'MOZ_ANDROID_MLS_STUMBLER',
'MOZ_ANDROID_SEARCH_ACTIVITY',
'MOZ_CRASHREPORTER',
'MOZ_DEBUG',
'MOZ_INSTALL_TRACKING',
'MOZ_LOCALE_SWITCHER',
@ -62,6 +64,7 @@ def _defines():
for var in ('ANDROID_CPU_ARCH',
'ANDROID_PACKAGE_NAME',
'GRE_MILESTONE',
'MOZ_ANDROID_SHARED_ID',
'MOZ_ANDROID_APPLICATION_CLASS',
'MOZ_ANDROID_BROWSER_INTENT_CLASS',
'MOZ_ANDROID_SEARCH_INTENT_CLASS',
@ -73,7 +76,6 @@ def _defines():
'MOZ_APP_VENDOR',
'MOZ_APP_VERSION',
'MOZ_CHILD_PROCESS_NAME',
'MOZ_CRASHREPORTER',
'MOZ_MOZILLA_API_KEY',
'MOZ_UPDATE_CHANNEL',
'OMNIJAR_NAME',
@ -101,6 +103,19 @@ def _defines():
DEFINES['MOZ_BUILDID'] = open(os.path.join(buildconfig.topobjdir, 'buildid.h')).readline().split()[2]
# Set the appropriate version code if not set by MOZ_APP_ANDROID_VERSION_CODE.
if CONFIG.get('MOZ_APP_ANDROID_VERSION_CODE'):
DEFINES['ANDROID_VERSION_CODE'] = \
CONFIG.get('MOZ_APP_ANDROID_VERSION_CODE')
else:
min_sdk = int(CONFIG.get('MOZ_ANDROID_MIN_SDK_VERSION') or '0') or None
max_sdk = int(CONFIG.get('MOZ_ANDROID_MAX_SDK_VERSION') or '0') or None
DEFINES['ANDROID_VERSION_CODE'] = android_version_code(
buildid=DEFINES['MOZ_BUILDID'],
cpu_arch=CONFIG['ANDROID_CPU_ARCH'],
min_sdk=min_sdk,
max_sdk=max_sdk)
return DEFINES
@ -108,6 +123,15 @@ def generate_java(output_file, input_filename):
includes = preprocessor.preprocess(includes=[input_filename],
defines=_defines(),
output=output_file,
marker="//#")
marker='//#')
includes.add(os.path.join(buildconfig.topobjdir, 'buildid.h'))
return includes
def generate_android_manifest(output_file, input_filename):
includes = preprocessor.preprocess(includes=[input_filename],
defines=_defines(),
output=output_file,
marker='#')
includes.add(os.path.join(buildconfig.topobjdir, 'buildid.h'))
return includes

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

@ -22,6 +22,9 @@ import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
import org.mozilla.gecko.Tabs.TabEvents;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.bookmarks.BookmarkEditFragment;
import org.mozilla.gecko.bookmarks.BookmarkUtils;
import org.mozilla.gecko.bookmarks.EditBookmarkTask;
import org.mozilla.gecko.cleanup.FileCleanupController;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserDB;
@ -166,8 +169,6 @@ import android.widget.RelativeLayout;
import android.widget.ViewFlipper;
import org.mozilla.gecko.switchboard.AsyncConfigLoader;
import org.mozilla.gecko.switchboard.SwitchBoard;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import java.io.File;
import java.io.FileNotFoundException;
@ -195,7 +196,8 @@ public class BrowserApp extends GeckoApp
OnUrlOpenInBackgroundListener,
AnchoredPopup.OnVisibilityChangeListener,
ActionModePresenter,
LayoutInflater.Factory {
LayoutInflater.Factory,
BookmarkEditFragment.Callbacks {
private static final String LOGTAG = "GeckoBrowserApp";
private static final int TABS_ANIMATION_DURATION = 450;
@ -4116,4 +4118,22 @@ public class BrowserApp extends GeckoApp
Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, method, "restricted");
}
}
/**
* Launch edit bookmark dialog. The {@link BookmarkEditFragment} needs to be started by an activity
* that implements the interface({@link BookmarkEditFragment.Callbacks}) for handling callback method.
*/
public void showEditBookmarkDialog(String pageUrl) {
if (BookmarkUtils.isEnabled(this)) {
BookmarkEditFragment dialog = BookmarkEditFragment.newInstance(pageUrl);
dialog.show(getSupportFragmentManager(), "edit-bookmark");
} else {
new EditBookmarkDialog(this).show(pageUrl);
}
}
@Override
public void onEditBookmark(@NonNull Bundle bundle) {
new EditBookmarkTask(this, bundle).execute();
}
}

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

@ -68,6 +68,9 @@ public class Experiments {
// Tabs tray: Arrange tabs in two columns in portrait mode
public static final String COMPACT_TABS = "compact-tabs";
// Enable full bookmark management(full-page dialog, bookmark/folder modification, etc.)
public static final String FULL_BOOKMARK_MANAGEMENT = "full-bookmark-management";
/**
* Returns if a user is in certain local experiment.
* @param experiment Name of experiment to look up

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

@ -1883,14 +1883,18 @@ public abstract class GeckoApp
}
@RobocopTarget
public static EventDispatcher getEventDispatcher() {
public static @NonNull EventDispatcher getEventDispatcher() {
final GeckoApp geckoApp = (GeckoApp) GeckoAppShell.getGeckoInterface();
return geckoApp.getAppEventDispatcher();
}
@Override
public EventDispatcher getAppEventDispatcher() {
return mLayerView != null ? mLayerView.getEventDispatcher() : null;
public @NonNull EventDispatcher getAppEventDispatcher() {
if (mLayerView == null) {
throw new IllegalStateException("Must not call getAppEventDispatcher() until after onCreate()");
}
return mLayerView.getEventDispatcher();
}
@Override

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

@ -0,0 +1,498 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.bookmarks;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
import org.mozilla.gecko.db.BrowserDB;
import java.lang.ref.WeakReference;
/**
* A dialog fragment that allows editing bookmark's url, title and changing the parent."
*/
public class BookmarkEditFragment extends DialogFragment {
private static final String ARG_ID = "id";
private static final String ARG_URL = "url";
private static final String ARG_BOOKMARK = "bookmark";
private long bookmarkId;
private String url;
private Bookmark bookmark;
private Toolbar toolbar;
private EditText nameText;
private TextInputLayout locationLayout;
private EditText locationText;
private EditText folderText;
public interface Callbacks {
/**
* A callback method to tell caller that bookmark has been modified.
* Caller takes charge for the change(e.g. update database).
*/
void onEditBookmark(Bundle bundle);
}
private Callbacks callbacks;
public static BookmarkEditFragment newInstance(long id) {
final Bundle args = new Bundle();
args.putLong(ARG_ID, id);
return newInstance(args);
}
public static BookmarkEditFragment newInstance(String url) {
final Bundle args = new Bundle();
args.putString(ARG_URL, url);
return newInstance(args);
}
private static BookmarkEditFragment newInstance(Bundle args) {
final BookmarkEditFragment fragment = new BookmarkEditFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
Fragment fragment = getTargetFragment();
if (fragment != null && fragment instanceof Callbacks) {
callbacks = (Callbacks) fragment;
} else if (context instanceof Callbacks) {
callbacks = (Callbacks) context;
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Apply DialogWhenLarge theme
setStyle(DialogFragment.STYLE_NO_TITLE, R.style.Bookmark_Gecko);
Bundle args = getArguments();
bookmarkId = args.getLong(ARG_ID);
url = args.getString(ARG_URL);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bookmark_edit_with_full_page, container);
toolbar = (Toolbar) view.findViewById(R.id.toolbar);
nameText = (EditText) view.findViewById(R.id.edit_bookmark_name);
locationLayout = (TextInputLayout) view.findViewById(R.id.edit_bookmark_location_layout);
locationText = (EditText) view.findViewById(R.id.edit_bookmark_location);
folderText = (EditText) view.findViewById(R.id.edit_parent_folder);
toolbar.inflateMenu(R.menu.bookmark_edit_menu);
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.done:
final String newUrl = locationText.getText().toString().trim();
final String newTitle = nameText.getText().toString();
if (callbacks != null) {
if (TextUtils.equals(newTitle, bookmark.originalTitle) &&
TextUtils.equals(newUrl, bookmark.originalUrl) &&
bookmark.parentId == bookmark.originalParentId) {
// Nothing changed, skip callback.
break;
}
final Bundle bundle = new Bundle();
bundle.putLong(Bookmarks._ID, bookmark.id);
bundle.putString(Bookmarks.TITLE, newTitle);
bundle.putString(Bookmarks.URL, newUrl);
bundle.putString(Bookmarks.KEYWORD, bookmark.keyword);
if (bookmark.parentId != bookmark.originalParentId) {
bundle.putLong(Bookmarks.PARENT, bookmark.parentId);
bundle.putLong(BrowserContract.PARAM_OLD_BOOKMARK_PARENT, bookmark.originalParentId);
}
callbacks.onEditBookmark(bundle);
}
break;
}
dismiss();
return true;
}
});
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
return view;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
final Bookmark bookmark = savedInstanceState.getParcelable(ARG_BOOKMARK);
if (bookmark != null) {
invalidateView(bookmark);
return;
}
}
getLoaderManager().initLoader(0, null, new BookmarkLoaderCallbacks());
}
@Override
public void onDestroyView() {
super.onDestroyView();
getLoaderManager().destroyLoader(0);
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (bookmark != null) {
bookmark.url = locationText.getText().toString().trim();
bookmark.title = nameText.getText().toString();
bookmark.folder = folderText.getText().toString();
outState.putParcelable(ARG_BOOKMARK, bookmark);
}
super.onSaveInstanceState(outState);
}
private void invalidateView(Bookmark bookmark) {
this.bookmark = bookmark;
nameText.setText(bookmark.title);
if (bookmark.type == Bookmarks.TYPE_FOLDER) {
locationLayout.setVisibility(View.GONE);
} else {
locationLayout.setVisibility(View.VISIBLE);
}
locationText.setText(bookmark.url);
if (Bookmarks.MOBILE_FOLDER_GUID.equals(bookmark.guid)) {
folderText.setText(R.string.bookmarks_folder_mobile);
} else {
folderText.setText(bookmark.folder);
}
// Enable menu item after bookmark is set to view
final MenuItem doneItem = toolbar.getMenu().findItem(R.id.done);
doneItem.setEnabled(true);
// Add a TextWatcher to prevent invalid input(e.g. empty string).
if (bookmark.type == Bookmarks.TYPE_FOLDER) {
BookmarkTextWatcher nameTextWatcher = new BookmarkTextWatcher(doneItem);
nameText.addTextChangedListener(nameTextWatcher);
} else {
BookmarkTextWatcher locationTextWatcher = new BookmarkTextWatcher(doneItem);
locationText.addTextChangedListener(locationTextWatcher);
}
}
/**
* A private struct to make it easier to pass bookmark data across threads
*/
private static class Bookmark implements Parcelable {
// Cannot be modified in this fragment.
final long id;
final String keyword;
final int type; // folder or bookmark
final String guid;
final String originalTitle;
final String originalUrl;
final long originalParentId;
final String originalFolder;
// Can be modified in this fragment.
String title;
String url;
long parentId;
String folder;
public Bookmark(long id, String url, String title, String keyword, long parentId,
String folder, int type, String guid) {
this(id, url, title, keyword, parentId, folder, type, guid, url, title, parentId, folder);
}
private Bookmark(long id, String originalUrl, String originalTitle, String keyword,
long originalParentId, String originalFolder, int type, String guid,
String modifiedUrl, String modifiedTitle, long modifiedParentId, String modifiedFolder) {
this.id = id;
this.originalUrl = originalUrl;
this.originalTitle = originalTitle;
this.keyword = keyword;
this.originalParentId = originalParentId;
this.originalFolder = originalFolder;
this.type = type;
this.guid = guid;
this.url = modifiedUrl;
this.title = modifiedTitle;
this.parentId = modifiedParentId;
this.folder = modifiedFolder;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeLong(id);
parcel.writeString(url);
parcel.writeString(title);
parcel.writeString(keyword);
parcel.writeLong(parentId);
parcel.writeString(folder);
parcel.writeInt(type);
parcel.writeString(guid);
parcel.writeString(originalUrl);
parcel.writeString(originalTitle);
parcel.writeLong(originalParentId);
parcel.writeString(originalFolder);
}
public static final Creator<Bookmark> CREATOR = new Creator<Bookmark>() {
@Override
public Bookmark createFromParcel(final Parcel source) {
final long id = source.readLong();
final String modifiedUrl = source.readString();
final String modifiedTitle = source.readString();
final String keyword = source.readString();
final long modifiedParentId = source.readLong();
final String modifiedFolder = source.readString();
final int type = source.readInt();
final String guid = source.readString();
final String originalUrl = source.readString();
final String originalTitle = source.readString();
final long originalParentId = source.readLong();
final String originalFolder = source.readString();
return new Bookmark(id, originalUrl, originalTitle, keyword, originalParentId, originalFolder,
type, guid, modifiedUrl, modifiedTitle, modifiedParentId, modifiedFolder);
}
@Override
public Bookmark[] newArray(final int size) {
return new Bookmark[size];
}
};
}
private class BookmarkLoaderCallbacks implements LoaderManager.LoaderCallbacks<Bookmark> {
@Override
public Loader<Bookmark> onCreateLoader(int id, Bundle args) {
return new BookmarkLoader(getContext(), bookmarkId, url);
}
@Override
public void onLoadFinished(Loader<Bookmark> loader, final Bookmark bookmark) {
if (bookmark == null) {
return;
}
invalidateView(bookmark);
}
@Override
public void onLoaderReset(Loader<Bookmark> loader) {
}
}
/**
* An AsyncTaskLoader to load {@link Bookmark} from a cursor.
*/
private static class BookmarkLoader extends AsyncTaskLoader<Bookmark> {
private final long bookmarkId;
private final String url;
private final ContentResolver contentResolver;
private final BrowserDB db;
private Bookmark bookmark;
private BookmarkLoader(Context context, long id, String url) {
super(context);
this.bookmarkId = id;
this.url = url;
this.contentResolver = context.getContentResolver();
this.db = BrowserDB.from(context);
}
@Override
public Bookmark loadInBackground() {
final Cursor cursor;
if (url != null) {
cursor = db.getBookmarkForUrl(contentResolver, url);
} else {
cursor = db.getBookmarkById(contentResolver, bookmarkId);
}
if (cursor == null) {
return null;
}
Bookmark bookmark = null;
try {
if (cursor.moveToFirst()) {
final long id = cursor.getLong(cursor.getColumnIndexOrThrow(Bookmarks._ID));
final String url = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.URL));
final String title = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.TITLE));
final String keyword = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.KEYWORD));
final long parentId = cursor.getLong(cursor.getColumnIndexOrThrow(Bookmarks.PARENT));
final String parentName = queryParentName(parentId);
final int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE));
final String guid = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.GUID));
bookmark = new Bookmark(id, url, title, keyword, parentId, parentName, type, guid);
}
} finally {
cursor.close();
}
return bookmark;
}
private String queryParentName(long folderId) {
Cursor cursor = db.getBookmarkById(contentResolver, folderId);
if (cursor == null) {
return "";
}
String folderName = "";
try {
if (cursor.moveToFirst()) {
final String guid = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.GUID));
if (Bookmarks.MOBILE_FOLDER_GUID.equals(guid)) {
folderName = getContext().getString(R.string.bookmarks_folder_mobile);
} else {
folderName = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.TITLE));
}
}
} finally {
cursor.close();
}
return folderName;
}
@Override
public void deliverResult(Bookmark bookmark) {
if (isReset()) {
this.bookmark = null;
return;
}
this.bookmark = bookmark;
if (isStarted()) {
super.deliverResult(bookmark);
}
}
@Override
protected void onStartLoading() {
if (bookmark != null) {
deliverResult(bookmark);
}
if (takeContentChanged() || bookmark == null) {
forceLoad();
}
}
@Override
protected void onStopLoading() {
cancelLoad();
}
@Override
public void onCanceled(Bookmark bookmark) {
this.bookmark = null;
}
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped.
onStopLoading();
bookmark = null;
}
}
/**
* This text watcher enables the menu item if the dialog contains valid information, or disables otherwise.
*/
private class BookmarkTextWatcher implements TextWatcher {
// A stored reference to the dialog containing the text field being watched.
private final WeakReference<MenuItem> doneItemWeakReference;
// Whether or not the menu item should be enabled.
private boolean enabled = true;
private BookmarkTextWatcher(MenuItem doneItem) {
doneItemWeakReference = new WeakReference<>(doneItem);
}
public boolean isEnabled() {
return enabled;
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Disables the menu item if the input field is empty.
final boolean enabled = (s.toString().trim().length() > 0);
final MenuItem doneItem = doneItemWeakReference.get();
if (doneItem != null) {
doneItem.setEnabled(enabled);
}
}
@Override
public void afterTextChanged(Editable s) {}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
}
}

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

@ -0,0 +1,24 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.bookmarks;
import android.content.Context;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.Experiments;
import org.mozilla.gecko.switchboard.SwitchBoard;
public class BookmarkUtils {
/**
* Get the switch from {@link SwitchBoard}. It's used to enable/disable
* full bookmark management features(full-page dialog, bookmark/folder modification, etc.)
*/
public static boolean isEnabled(Context context) {
return AppConstants.NIGHTLY_BUILD &&
SwitchBoard.isInExperiment(context, Experiments.FULL_BOOKMARK_MANAGEMENT);
}
}

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

@ -0,0 +1,68 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.bookmarks;
import android.app.Activity;
import android.content.ContentResolver;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import org.mozilla.gecko.R;
import org.mozilla.gecko.SnackbarBuilder;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UIAsyncTask;
import java.lang.ref.WeakReference;
public class EditBookmarkTask extends UIAsyncTask.WithoutParams<Void> {
private final WeakReference<Activity> activityWeakReference;
private final BrowserDB db;
private final ContentResolver contentResolver;
private final Bundle bundle;
public EditBookmarkTask(Activity activity, @NonNull Bundle bundle) {
super(ThreadUtils.getBackgroundHandler());
this.activityWeakReference = new WeakReference<>(activity);
this.db = BrowserDB.from(activity);
this.contentResolver = activity.getContentResolver();
this.bundle = bundle;
}
@Override
public Void doInBackground() {
final long bookmarkId = bundle.getLong(BrowserContract.Bookmarks._ID);
final String url = bundle.getString(BrowserContract.Bookmarks.URL);
final String title = bundle.getString(BrowserContract.Bookmarks.TITLE);
final String keyword = bundle.getString(BrowserContract.Bookmarks.KEYWORD);
if (bundle.containsKey(BrowserContract.Bookmarks.PARENT) &&
bundle.containsKey(BrowserContract.PARAM_OLD_BOOKMARK_PARENT)) {
final long newParentId = bundle.getLong(BrowserContract.Bookmarks.PARENT);
final long oldParentId = bundle.getLong(BrowserContract.PARAM_OLD_BOOKMARK_PARENT);
db.updateBookmark(contentResolver, bookmarkId, url, title, keyword, newParentId, oldParentId);
} else {
db.updateBookmark(contentResolver, bookmarkId, url, title, keyword);
}
return null;
}
@Override
public void onPostExecute(Void result) {
final Activity activity = activityWeakReference.get();
if (activity == null || activity.isFinishing()) {
return;
}
SnackbarBuilder.builder(activity)
.message(R.string.bookmark_updated)
.duration(Snackbar.LENGTH_LONG)
.buildAndShow();
}
}

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

@ -50,6 +50,7 @@ public class BrowserContract {
public static final String PARAM_IS_SYNC = "sync";
public static final String PARAM_SHOW_DELETED = "show_deleted";
public static final String PARAM_IS_TEST = "test";
public static final String PARAM_OLD_BOOKMARK_PARENT = "old_bookmark_parent";
public static final String PARAM_INSERT_IF_NEEDED = "insert_if_needed";
public static final String PARAM_INCREMENT_VISITS = "increment_visits";
public static final String PARAM_INCREMENT_REMOTE_AGGREGATES = "increment_remote_aggregates";

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

@ -22,6 +22,7 @@ import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.support.v4.content.CursorLoader;
/**
@ -106,11 +107,15 @@ public abstract class BrowserDB {
public abstract boolean isBookmark(ContentResolver cr, String uri);
public abstract boolean addBookmark(ContentResolver cr, String title, String uri);
public abstract Uri addBookmarkFolder(ContentResolver cr, String title, long parentId);
public abstract Cursor getBookmarkForUrl(ContentResolver cr, String url);
public abstract Cursor getBookmarksForPartialUrl(ContentResolver cr, String partialUrl);
public abstract Cursor getBookmarkById(ContentResolver cr, long id);
public abstract void removeBookmarksWithURL(ContentResolver cr, String uri);
public abstract void removeBookmarkWithId(ContentResolver cr, long id);
public abstract void registerBookmarkObserver(ContentResolver cr, ContentObserver observer);
public abstract void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword);
public abstract void updateBookmark(ContentResolver cr, long id, String uri, String title, String keyword);
public abstract void updateBookmark(ContentResolver cr, long id, String uri, String title, String keyword, long newParentId, long oldParentId);
public abstract boolean hasBookmarkWithGuid(ContentResolver cr, String guid);
public abstract boolean insertPageMetadata(ContentProviderClient contentProviderClient, String pageUrl, boolean hasImage, String metadataJSON);

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

@ -15,7 +15,6 @@ import java.util.Map;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.R;
import org.mozilla.gecko.activitystream.ranking.HighlightsRanking;
import org.mozilla.gecko.db.BrowserContract.ActivityStreamBlocklist;
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
import org.mozilla.gecko.db.BrowserContract.Combined;
@ -31,7 +30,6 @@ import org.mozilla.gecko.db.BrowserContract.TopSites;
import org.mozilla.gecko.db.BrowserContract.UrlAnnotations;
import org.mozilla.gecko.db.BrowserContract.PageMetadata;
import org.mozilla.gecko.db.DBUtils.UpdateOperation;
import org.mozilla.gecko.home.activitystream.model.Highlight;
import org.mozilla.gecko.icons.IconsHelper;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
@ -50,7 +48,6 @@ import android.content.UriMatcher;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteCursor;
@ -1505,7 +1502,12 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
* that match.
*/
private int updateBookmarkParents(SQLiteDatabase db, ContentValues values, String selection, String[] selectionArgs) {
trace("Updating bookmark parents of " + selection + " (" + selectionArgs[0] + ")");
if (selectionArgs != null) {
trace("Updating bookmark parents of " + selection + " (" + selectionArgs[0] + ")");
} else {
trace("Updating bookmark parents of " + selection);
}
String where = Bookmarks._ID + " IN (" +
" SELECT DISTINCT " + Bookmarks.PARENT +
" FROM " + TABLE_BOOKMARKS +
@ -1546,7 +1548,32 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
debug("Inserting bookmark in database with URL: " + url);
final SQLiteDatabase db = getWritableDatabase(uri);
beginWrite(db);
return db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.TITLE, values);
final long insertedId = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.TITLE, values);
if (insertedId == -1) {
Log.e(LOGTAG, "Unable to insert bookmark in database with URL: " + url);
return insertedId;
}
if (isCallerSync(uri)) {
// Sync will handle timestamps on its own, so we don't perform the update here.
return insertedId;
}
// Bump parent's lastModified timestamp.
final long lastModified = values.getAsLong(Bookmarks.DATE_MODIFIED);
final ContentValues parentValues = new ContentValues();
parentValues.put(Bookmarks.DATE_MODIFIED, lastModified);
// The ContentValues should have parentId, or the insertion above would fail because of
// database schema foreign key constraint.
final long parentId = values.getAsLong(Bookmarks.PARENT);
db.update(TABLE_BOOKMARKS,
parentValues,
Bookmarks._ID + " = ?",
new String[] { String.valueOf(parentId) });
return insertedId;
}
@ -1595,7 +1622,53 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
}
beginWrite(db);
return db.update(TABLE_BOOKMARKS, values, inClause, null);
final int updated = db.update(TABLE_BOOKMARKS, values, inClause, null);
if (updated == 0) {
trace("No update on URI: " + uri);
return updated;
}
if (isCallerSync(uri)) {
// Sync will handle timestamps on its own, so we don't perform the update here.
return updated;
}
final long oldParentId = getOldParentIdIfParentChanged(uri);
if (oldParentId == -1) {
// Parent isn't changed, don't bump its timestamps.
return updated;
}
final long newParentId = values.getAsLong(Bookmarks.PARENT);
final long lastModified = values.getAsLong(Bookmarks.DATE_MODIFIED);
final ContentValues parentValues = new ContentValues();
parentValues.put(Bookmarks.DATE_MODIFIED, lastModified);
// Bump old/new parent's lastModified timestamps.
db.update(TABLE_BOOKMARKS, parentValues,
Bookmarks._ID + " in (?, ?)",
new String[] { String.valueOf(oldParentId), String.valueOf(newParentId) });
return updated;
}
/**
* Use the query key {@link BrowserContract#PARAM_OLD_BOOKMARK_PARENT} to check if parent is changed or not.
*
* @return old parent id if uri has the key, or -1 otherwise.
*/
private long getOldParentIdIfParentChanged(Uri uri) {
final String oldParentId = uri.getQueryParameter(BrowserContract.PARAM_OLD_BOOKMARK_PARENT);
if (TextUtils.isEmpty(oldParentId)) {
return -1;
}
try {
return Long.parseLong(oldParentId);
} catch (NumberFormatException ignored) {
return -1;
}
}
private long insertHistory(Uri uri, ContentValues values) {
@ -2098,15 +2171,20 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
debug("Deleting bookmarks for URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
beginWrite(db);
if (isCallerSync(uri)) {
beginWrite(db);
return db.delete(TABLE_BOOKMARKS, selection, selectionArgs);
}
debug("Marking bookmarks as deleted for URI: " + uri);
ContentValues values = new ContentValues();
// Bump parent's lastModified timestamp before record deleted.
final ContentValues parentValues = new ContentValues();
parentValues.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
updateBookmarkParents(db, parentValues, selection, selectionArgs);
final ContentValues values = new ContentValues();
values.put(Bookmarks.IS_DELETED, 1);
values.put(Bookmarks.POSITION, 0);
values.putNull(Bookmarks.PARENT);

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

@ -36,7 +36,6 @@ import org.mozilla.gecko.db.BrowserContract.History;
import org.mozilla.gecko.db.BrowserContract.SyncColumns;
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
import org.mozilla.gecko.db.BrowserContract.TopSites;
import org.mozilla.gecko.db.BrowserContract.Highlights;
import org.mozilla.gecko.db.BrowserContract.PageMetadata;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.icons.decoders.FaviconDecoder;
@ -1121,20 +1120,6 @@ public class LocalBrowserDB extends BrowserDB {
}
}
/**
* Find parents of records that match the provided criteria, and bump their
* modified timestamp.
*/
protected void bumpParents(ContentResolver cr, String param, String value) {
ContentValues values = new ContentValues();
values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
String where = param + " = ?";
String[] args = new String[] { value };
int updated = cr.update(mParentsUriWithProfile, values, where, args);
debug("Updated " + updated + " rows to new modified time.");
}
private void addBookmarkItem(ContentResolver cr, String title, String uri, long folderId) {
final long now = System.currentTimeMillis();
ContentValues values = new ContentValues();
@ -1175,16 +1160,7 @@ public class LocalBrowserDB extends BrowserDB {
Bookmarks.PARENT + " = " + folderId,
new String[] { uri });
// Bump parent modified time using its ID.
debug("Bumping parent modified time for addition to: " + folderId);
final String where = Bookmarks._ID + " = ?";
final String[] args = new String[] { String.valueOf(folderId) };
ContentValues bumped = new ContentValues();
bumped.put(Bookmarks.DATE_MODIFIED, now);
final int updated = cr.update(mBookmarksUriWithProfile, bumped, where, args);
debug("Updated " + updated + " rows to new modified time.");
// BrowserProvider will handle updating parent's lastModified timestamp, nothing else to do.
}
@Override
@ -1200,7 +1176,6 @@ public class LocalBrowserDB extends BrowserDB {
addBookmarkItem(cr, title, uri, folderId);
return true;
}
private boolean isBookmarkForUrlInFolder(ContentResolver cr, String uri, long folderId) {
final Cursor c = cr.query(bookmarksUriWithLimit(1),
new String[] { Bookmarks._ID },
@ -1219,18 +1194,36 @@ public class LocalBrowserDB extends BrowserDB {
}
}
@Override
public Uri addBookmarkFolder(ContentResolver cr, String title, long parentId) {
final ContentValues values = new ContentValues();
final long now = System.currentTimeMillis();
values.put(Bookmarks.DATE_CREATED, now);
values.put(Bookmarks.DATE_MODIFIED, now);
values.put(Bookmarks.GUID, Utils.generateGuid());
values.put(Bookmarks.PARENT, parentId);
values.put(Bookmarks.TITLE, title);
values.put(Bookmarks.TYPE, Bookmarks.TYPE_FOLDER);
// BrowserProvider will bump parent's lastModified timestamp after successful insertion.
return cr.insert(mBookmarksUriWithProfile, values);
}
@Override
@RobocopTarget
public void removeBookmarksWithURL(ContentResolver cr, String uri) {
Uri contentUri = mBookmarksUriWithProfile;
// BrowserProvider will bump parent's lastModified timestamp after successful deletion.
cr.delete(mBookmarksUriWithProfile,
Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " != ? ",
new String[] { uri, String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) });
}
// Do this now so that the items still exist!
bumpParents(cr, Bookmarks.URL, uri);
final String[] urlArgs = new String[] { uri, String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) };
final String urlEquals = Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " != ? ";
cr.delete(contentUri, urlEquals, urlArgs);
@Override
public void removeBookmarkWithId(ContentResolver cr, long id) {
// BrowserProvider will bump parent's lastModified timestamp after successful deletion.
cr.delete(mBookmarksUriWithProfile,
Bookmarks._ID + " = ? AND " + Bookmarks.PARENT + " != ? ",
new String[] { String.valueOf(id), String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) });
}
@Override
@ -1240,7 +1233,7 @@ public class LocalBrowserDB extends BrowserDB {
@Override
@RobocopTarget
public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword) {
public void updateBookmark(ContentResolver cr, long id, String uri, String title, String keyword) {
ContentValues values = new ContentValues();
values.put(Bookmarks.TITLE, title);
values.put(Bookmarks.URL, uri);
@ -1253,6 +1246,26 @@ public class LocalBrowserDB extends BrowserDB {
new String[] { String.valueOf(id) });
}
@Override
public void updateBookmark(ContentResolver cr, long id, String uri, String title, String keyword,
long newParentId, long oldParentId) {
final ContentValues values = new ContentValues();
values.put(Bookmarks.TITLE, title);
values.put(Bookmarks.URL, uri);
values.put(Bookmarks.KEYWORD, keyword);
values.put(Bookmarks.PARENT, newParentId);
values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
final Uri contentUri = mBookmarksUriWithProfile.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_OLD_BOOKMARK_PARENT,
String.valueOf(oldParentId))
.build();
cr.update(contentUri,
values,
Bookmarks._ID + " = ?",
new String[] { String.valueOf(id) });
}
@Override
public boolean hasBookmarkWithGuid(ContentResolver cr, String guid) {
Cursor c = cr.query(bookmarksUriWithLimit(1),
@ -1785,6 +1798,9 @@ public class LocalBrowserDB extends BrowserDB {
new String[] { Bookmarks._ID,
Bookmarks.URL,
Bookmarks.TITLE,
Bookmarks.TYPE,
Bookmarks.PARENT,
Bookmarks.GUID,
Bookmarks.KEYWORD },
Bookmarks.URL + " = ?",
new String[] { url },
@ -1798,6 +1814,28 @@ public class LocalBrowserDB extends BrowserDB {
return c;
}
@Override
public Cursor getBookmarkById(ContentResolver cr, long id) {
final Cursor c = cr.query(mBookmarksUriWithProfile,
new String[] { Bookmarks._ID,
Bookmarks.URL,
Bookmarks.TITLE,
Bookmarks.TYPE,
Bookmarks.PARENT,
Bookmarks.GUID,
Bookmarks.KEYWORD },
Bookmarks._ID + " = ?",
new String[] { String.valueOf(id) },
null);
if (c != null && c.getCount() == 0) {
c.close();
return null;
}
return c;
}
@Override
public Cursor getBookmarksForPartialUrl(ContentResolver cr, String partialUrl) {
Cursor c = cr.query(mBookmarksUriWithProfile,

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

@ -10,18 +10,13 @@ import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.View;
import android.widget.ListView;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.EditBookmarkDialog;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.R;
@ -38,8 +33,6 @@ import org.mozilla.gecko.util.DrawableUtil;
import org.mozilla.gecko.util.GeckoBundle;
import org.mozilla.gecko.util.ThreadUtils;
import java.lang.ref.WeakReference;
/**
* Delegate to watch for bookmark state changes.
*
@ -177,7 +170,7 @@ public class BookmarkStateChangeDelegate extends BrowserAppDelegateWithReference
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION,
TelemetryContract.Method.DIALOG, extrasId);
new EditBookmarkDialog(browserApp).show(tab.getURL());
browserApp.showEditBookmarkDialog(tab.getURL());
} else if (itemId == 1) {
final String extrasId = res.getResourceEntryName(R.string.contextmenu_add_to_launcher);

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

@ -63,6 +63,7 @@ public class BookmarkFolderView extends LinearLayout {
private final TextView mSubtitle;
private final ImageView mIcon;
private final ImageView mIndicator;
public BookmarkFolderView(Context context) {
this(context, null);
@ -76,6 +77,7 @@ public class BookmarkFolderView extends LinearLayout {
mTitle = (TextView) findViewById(R.id.title);
mSubtitle = (TextView) findViewById(R.id.subtitle);
mIcon = (ImageView) findViewById(R.id.icon);
mIndicator = (ImageView) findViewById(R.id.indicator);
}
public void update(String title, int folderID) {
@ -143,5 +145,11 @@ public class BookmarkFolderView extends LinearLayout {
public void setState(@NonNull FolderState state) {
mIcon.setImageResource(state.image);
if (state == FolderState.PARENT) {
mIndicator.setVisibility(View.GONE);
} else {
mIndicator.setVisibility(View.VISIBLE);
}
}
}

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

@ -9,9 +9,12 @@ import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.mozilla.gecko.EditBookmarkDialog;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.PrefsHelper;
import org.mozilla.gecko.R;
import org.mozilla.gecko.bookmarks.BookmarkEditFragment;
import org.mozilla.gecko.bookmarks.BookmarkUtils;
import org.mozilla.gecko.bookmarks.EditBookmarkTask;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
import org.mozilla.gecko.db.BrowserDB;
@ -21,13 +24,11 @@ import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener;
import org.mozilla.gecko.home.BookmarksListAdapter.RefreshType;
import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.media.MediaControlService;
import org.mozilla.gecko.preferences.GeckoPreferences;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.database.Cursor;
import android.database.MergeCursor;
@ -35,7 +36,9 @@ import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
@ -45,11 +48,9 @@ import android.widget.TextView;
/**
* A page in about:home that displays a ListView of bookmarks.
*/
public class BookmarksPanel extends HomeFragment {
public class BookmarksPanel extends HomeFragment implements BookmarkEditFragment.Callbacks {
public static final String LOGTAG = "GeckoBookmarksPanel";
public static final String BOOKMARK_MOBILE_FOLDER_PREF = "ui.bookmark.mobilefolder.enabled";
// Cursor loader ID for list of bookmarks.
private static final int LOADER_ID_BOOKMARKS_LIST = 0;
@ -62,12 +63,6 @@ public class BookmarksPanel extends HomeFragment {
// Position that the list view should be scrolled to after loading has finished.
private static final String BOOKMARKS_SCROLL_POSITION = "listview_position";
private final String[] mPrefs = { BOOKMARK_MOBILE_FOLDER_PREF };
private PrefsHelper.PrefHandler mPrefsObserver;
private boolean mIfMobileFolderPrefOn = true;
// List of bookmarks.
private BookmarksListView mList;
@ -102,9 +97,6 @@ public class BookmarksPanel extends HomeFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setupPrefHandler();
final View view = inflater.inflate(R.layout.home_bookmarks_panel, container, false);
mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);
@ -129,18 +121,6 @@ public class BookmarksPanel extends HomeFragment {
return view;
}
private void setupPrefHandler() {
mPrefsObserver = new PrefsHelper.PrefHandlerBase() {
@Override
public void prefValue(String pref, boolean value) {
if (pref.equals(BOOKMARK_MOBILE_FOLDER_PREF)) {
mIfMobileFolderPrefOn = value;
}
}
};
PrefsHelper.addObserver(mPrefs, mPrefsObserver);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@ -187,6 +167,38 @@ public class BookmarksPanel extends HomeFragment {
loadIfVisible();
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (super.onContextItemSelected(item)) {
// HomeFragment was able to handle to selected item.
return true;
}
final ContextMenuInfo menuInfo = item.getMenuInfo();
if (!(menuInfo instanceof HomeContextMenuInfo)) {
return false;
}
final HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
final int itemId = item.getItemId();
final Context context = getContext();
if (itemId == R.id.home_edit_bookmark) {
if (BookmarkUtils.isEnabled(getContext())) {
final BookmarkEditFragment dialog = BookmarkEditFragment.newInstance(info.bookmarkId);
dialog.setTargetFragment(this, 0);
dialog.show(getFragmentManager(), "edit-bookmark");
} else {
// UI Dialog associates to the activity context, not the applications'.
new EditBookmarkDialog(context).show(info.url);
}
return true;
}
return false;
}
@Override
public void onDestroyView() {
mList = null;
@ -220,6 +232,11 @@ public class BookmarksPanel extends HomeFragment {
getLoaderManager().initLoader(LOADER_ID_BOOKMARKS_LIST, bundle, mLoaderCallbacks);
}
@Override
public void onEditBookmark(@NonNull Bundle bundle) {
new EditBookmarkTask(getActivity(), bundle).execute();
}
private void updateUiFromCursor(Cursor c) {
if ((c == null || c.getCount() == 0) && mEmptyView == null) {
// Set empty page view. We delay this so that the empty view won't flash.
@ -331,7 +348,7 @@ public class BookmarksPanel extends HomeFragment {
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
BookmarksLoader bl = (BookmarksLoader) loader;
final BookmarksLoader bl = (BookmarksLoader) loader;
mListAdapter.swapCursor(c, bl.getFolderInfo(), bl.getRefreshType());
if (mPanelStateChangeListener != null) {
@ -350,7 +367,7 @@ public class BookmarksPanel extends HomeFragment {
// BrowserDB updates (e.g. through sync, or when opening a new tab) will trigger
// a refresh which reuses the same loader - in that case we don't want to reset
// the scroll position again.
int currentLoaderHash = bl.hashCode();
final int currentLoaderHash = bl.hashCode();
if (mList != null && currentLoaderHash != mLastLoaderHash) {
mList.setSelection(bl.getTargetPosition());
mLastLoaderHash = currentLoaderHash;

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

@ -442,7 +442,7 @@ public class BrowserSearch extends HomeFragment
// Position for Top Sites grid items, but will always be -1 since this is only for BrowserSearch result
final int position = -1;
new RemoveItemByUrlTask(context, info.url, info.itemType, position).execute();
new RemoveItemTask(getActivity(), info, position).execute();
return true;
}

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

@ -5,9 +5,9 @@
package org.mozilla.gecko.home;
import java.lang.ref.WeakReference;
import java.util.EnumSet;
import org.mozilla.gecko.EditBookmarkDialog;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.GeckoProfile;
@ -17,7 +17,6 @@ import org.mozilla.gecko.R;
import org.mozilla.gecko.SnackbarBuilder;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
@ -296,20 +295,14 @@ public abstract class HomeFragment extends Fragment {
return true;
}
if (itemId == R.id.home_edit_bookmark) {
// UI Dialog associates to the activity context, not the applications'.
new EditBookmarkDialog(context).show(info.url);
return true;
}
if (itemId == R.id.home_remove) {
// For Top Sites grid items, position is required in case item is Pinned.
final int position = info instanceof TopSitesGridContextMenuInfo ? info.position : -1;
if (info.hasPartnerBookmarkId()) {
new RemovePartnerBookmarkTask(context, info.bookmarkId).execute();
new RemovePartnerBookmarkTask(getActivity(), info.bookmarkId).execute();
} else {
new RemoveItemByUrlTask(context, info.url, info.itemType, position).execute();
new RemoveItemTask(getActivity(), info, position).execute();
}
return true;
}
@ -395,39 +388,40 @@ public abstract class HomeFragment extends Fragment {
mIsLoaded = true;
}
protected static class RemoveItemByUrlTask extends UIAsyncTask.WithoutParams<Void> {
private final Context mContext;
private final String mUrl;
private final RemoveItemType mType;
private final int mPosition;
private final BrowserDB mDB;
static class RemoveItemTask extends UIAsyncTask.WithoutParams<Void> {
private final WeakReference<Activity> activityWeakReference;
private final Context context;
private final HomeContextMenuInfo info;
private final int position;
private final BrowserDB db;
/**
* Remove bookmark/history/reading list type item by url, and also unpin the
* Remove bookmark/history/reading list type item, and also unpin the
* Top Sites grid item at index <code>position</code>.
*/
public RemoveItemByUrlTask(Context context, String url, RemoveItemType type, int position) {
RemoveItemTask(Activity activity, HomeContextMenuInfo info, int position) {
super(ThreadUtils.getBackgroundHandler());
mContext = context;
mUrl = url;
mType = type;
mPosition = position;
mDB = BrowserDB.from(context);
this.activityWeakReference = new WeakReference<>(activity);
this.context = activity.getApplicationContext();
this.info = info;
this.position = position;
this.db = BrowserDB.from(context);
}
@Override
public Void doInBackground() {
ContentResolver cr = mContext.getContentResolver();
ContentResolver cr = context.getContentResolver();
if (mPosition > -1) {
mDB.unpinSite(cr, mPosition);
if (mDB.hideSuggestedSite(mUrl)) {
if (position > -1) {
db.unpinSite(cr, position);
if (db.hideSuggestedSite(info.url)) {
cr.notifyChange(SuggestedSites.CONTENT_URI, null);
}
}
switch (mType) {
final RemoveItemType type = info.itemType;
switch (type) {
case BOOKMARKS:
removeBookmark(cr);
break;
@ -442,7 +436,7 @@ public abstract class HomeFragment extends Fragment {
break;
default:
Log.e(LOGTAG, "Can't remove item type " + mType.toString());
Log.e(LOGTAG, "Can't remove item type " + type.toString());
break;
}
return null;
@ -450,15 +444,20 @@ public abstract class HomeFragment extends Fragment {
@Override
public void onPostExecute(Void result) {
SnackbarBuilder.builder((Activity) mContext)
final Activity activity = activityWeakReference.get();
if (activity == null || activity.isFinishing()) {
return;
}
SnackbarBuilder.builder(activity)
.message(R.string.page_removed)
.duration(Snackbar.LENGTH_LONG)
.buildAndShow();
}
private void removeBookmark(ContentResolver cr) {
SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(mContext);
final boolean isReaderViewPage = rch.isURLCached(mUrl);
SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(context);
final boolean isReaderViewPage = rch.isURLCached(info.url);
final String extra;
if (isReaderViewPage) {
@ -468,26 +467,28 @@ public abstract class HomeFragment extends Fragment {
}
Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.CONTEXT_MENU, extra);
mDB.removeBookmarksWithURL(cr, mUrl);
db.removeBookmarkWithId(cr, info.bookmarkId);
if (isReaderViewPage) {
ReadingListHelper.removeCachedReaderItem(mUrl, mContext);
ReadingListHelper.removeCachedReaderItem(info.url, context);
}
}
private void removeHistory(ContentResolver cr) {
mDB.removeHistoryEntry(cr, mUrl);
db.removeHistoryEntry(cr, info.url);
}
}
private static class RemovePartnerBookmarkTask extends UIAsyncTask.WithoutParams<Void> {
private final WeakReference<Activity> activityWeakReference;
private Context context;
private long bookmarkId;
public RemovePartnerBookmarkTask(Context context, long bookmarkId) {
private RemovePartnerBookmarkTask(Activity activity, long bookmarkId) {
super(ThreadUtils.getBackgroundHandler());
this.context = context;
this.activityWeakReference = new WeakReference<>(activity);
this.context = activity.getApplicationContext();
this.bookmarkId = bookmarkId;
}
@ -504,7 +505,12 @@ public abstract class HomeFragment extends Fragment {
@Override
protected void onPostExecute(Void aVoid) {
SnackbarBuilder.builder((Activity) context)
final Activity activity = activityWeakReference.get();
if (activity == null || activity.isFinishing()) {
return;
}
SnackbarBuilder.builder(activity)
.message(R.string.page_removed)
.duration(Snackbar.LENGTH_LONG)
.buildAndShow();

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

@ -31,17 +31,14 @@ public class IconGenerator implements IconLoader {
// Mozilla's Visual Design Colour Palette
// http://firefoxux.github.io/StyleGuide/#/visualDesign/colours
private static final int[] COLORS = {
0xFFc33c32,
0xFFf25820,
0xFFff9216,
0xFFffcb00,
0xFF57bd35,
0xFF01bdad,
0xFF0996f8,
0xFF02538b,
0xFF1f386e,
0xFF7a2f7a,
0xFFea385e,
0xFF9A4C00,
0xFFAB008D,
0xFF4C009C,
0xFF002E9C,
0xFF009EC2,
0xFF009D02,
0xFF51AB00,
0xFF36385A,
};
private static final int TEXT_SIZE_DP = 12;

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

@ -10,11 +10,14 @@ import org.mozilla.gecko.icons.IconCallback;
import org.mozilla.gecko.icons.IconResponse;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@ -33,7 +36,7 @@ public class FaviconView extends ImageView {
private static String DEFAULT_FAVICON_KEY = FaviconView.class.getSimpleName() + "DefaultFavicon";
// Default x/y-radius of the oval used to round the corners of the background (dp)
private static final int DEFAULT_CORNER_RADIUS_DP = 4;
private static final int DEFAULT_CORNER_RADIUS_DP = 2;
private Bitmap mIconBitmap;
@ -68,6 +71,8 @@ public class FaviconView extends ImageView {
// boolean switch for disabling rounded corners, value defined in attrs.xml .
private final boolean areRoundCornersEnabled;
private final Resources mResources;
// Initializing the static paints.
static {
sBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@ -94,6 +99,8 @@ public class FaviconView extends ImageView {
mBackgroundRect = new RectF(0, 0, 0, 0);
mBackgroundCornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_CORNER_RADIUS_DP, metrics);
mResources = getResources();
}
@Override
@ -147,7 +154,20 @@ public class FaviconView extends ImageView {
mScalingExpected = false;
}
setImageBitmap(mIconBitmap);
// In original, there is no round corners if FaviconView has bitmap icon. But the new design
// needs round corners all the time, so we use RoundedBitmapDrawableFactory to create round corners.
if (areRoundCornersEnabled) {
// mIconBitmap's size must bew small or equal to mActualWidth, or we cannot see the round corners.
if (mActualWidth < mIconBitmap.getWidth()) {
scaleBitmap();
}
RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(mResources, mIconBitmap);
roundedBitmapDrawable.setCornerRadius(mBackgroundCornerRadius);
roundedBitmapDrawable.setAntiAlias(true);
setImageDrawable(roundedBitmapDrawable);
} else {
setImageBitmap(mIconBitmap);
}
// After scaling, determine if we have empty space around the scaled image which we need to
// fill with the coloured background. If applicable, show it.

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

@ -75,6 +75,7 @@
<!ENTITY bookmark_removed "Bookmark removed">
<!ENTITY bookmark_updated "Bookmark updated">
<!ENTITY bookmark_options "Options">
<!ENTITY bookmark_save "Save">
<!ENTITY screenshot_added_to_bookmarks "Screenshot added to bookmarks">
<!-- Localization note (screenshot_folder_label_in_bookmarks): We save links to screenshots
the user takes. The folder we store these links in is located in the bookmarks list
@ -86,6 +87,7 @@
in the folder. -->
<!ENTITY bookmark_folder_items "&formatD; items">
<!ENTITY bookmark_folder_one_item "1 item">
<!ENTITY bookmark_parent_folder "Parent Folder">
<!ENTITY reader_saved_offline "Saved offline">
<!-- Localization note (reader_switch_to_bookmarks) : This

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

@ -119,6 +119,7 @@ DIRS += ['locales']
GENERATED_FILES += [
'../geckoview/generated/preprocessed/org/mozilla/geckoview/BuildConfig.java',
'AndroidManifest.xml',
'generated/preprocessed/org/mozilla/gecko/AdjustConstants.java',
'generated/preprocessed/org/mozilla/gecko/AppConstants.java',
]
@ -131,6 +132,9 @@ x.inputs += ['AdjustConstants.java.in']
y = GENERATED_FILES['generated/preprocessed/org/mozilla/gecko/AppConstants.java']
y.script = 'generate_build_config.py:generate_java'
y.inputs += ['AppConstants.java.in']
z = GENERATED_FILES['AndroidManifest.xml']
z.script = 'generate_build_config.py:generate_android_manifest'
z.inputs += ['AndroidManifest.xml.in']
include('android-services.mozbuild')
@ -476,6 +480,9 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'animation/Rotate3DAnimation.java',
'animation/ViewHelper.java',
'ANRReporter.java',
'bookmarks/BookmarkEditFragment.java',
'bookmarks/BookmarkUtils.java',
'bookmarks/EditBookmarkTask.java',
'BootReceiver.java',
'BrowserApp.java',
'BrowserLocaleManager.java',
@ -1226,45 +1233,6 @@ if CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY']:
'%' + CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY'] + '/assets',
]
# We do not expose MOZ_ADJUST_SDK_KEY here because that # would leak the value
# to build logs. Instead we expose the token quietly where appropriate in
# Makefile.in.
for var in ('MOZ_ANDROID_ANR_REPORTER', 'MOZ_DEBUG',
'MOZ_ANDROID_SEARCH_ACTIVITY', 'MOZ_NATIVE_DEVICES', 'MOZ_ANDROID_MLS_STUMBLER',
'MOZ_ANDROID_DOWNLOADS_INTEGRATION', 'MOZ_INSTALL_TRACKING',
'MOZ_ANDROID_GCM', 'MOZ_ANDROID_EXCLUDE_FONTS', 'MOZ_LOCALE_SWITCHER',
'MOZ_ANDROID_BEAM', 'MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE',
'MOZ_SWITCHBOARD', 'MOZ_ANDROID_CUSTOM_TABS',
'MOZ_ANDROID_ACTIVITY_STREAM'):
if CONFIG[var]:
DEFINES[var] = 1
for var in ('MOZ_UPDATER', 'MOZ_PKG_SPECIAL', 'MOZ_ANDROID_GCM_SENDERID'):
if CONFIG[var]:
DEFINES[var] = CONFIG[var]
for var in ('ANDROID_PACKAGE_NAME', 'ANDROID_CPU_ARCH',
'GRE_MILESTONE', 'MOZ_APP_BASENAME', 'MOZ_MOZILLA_API_KEY',
'MOZ_APP_DISPLAYNAME', 'MOZ_APP_UA_NAME', 'MOZ_APP_ID', 'MOZ_APP_NAME',
'MOZ_APP_VENDOR', 'MOZ_APP_VERSION', 'MOZ_CHILD_PROCESS_NAME',
'MOZ_ANDROID_APPLICATION_CLASS', 'MOZ_ANDROID_BROWSER_INTENT_CLASS', 'MOZ_ANDROID_SEARCH_INTENT_CLASS',
'MOZ_CRASHREPORTER', 'MOZ_UPDATE_CHANNEL', 'OMNIJAR_NAME',
'OS_TARGET', 'TARGET_XPCOM_ABI'):
DEFINES[var] = CONFIG[var]
# Mangle our package name to avoid Bug 750548.
DEFINES['MANGLED_ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME'].replace('fennec', 'f3nn3c')
DEFINES['MOZ_APP_ABI'] = CONFIG['TARGET_XPCOM_ABI']
if not CONFIG['COMPILE_ENVIRONMENT']:
# These should really come from the included binaries, but that's not easy.
DEFINES['MOZ_APP_ABI'] = 'arm-eabi-gcc3' # Observe quote differences here ...
DEFINES['TARGET_XPCOM_ABI'] = '"arm-eabi-gcc3"' # ... and here.
if '-march=armv7' in CONFIG['OS_CFLAGS']:
DEFINES['MOZ_MIN_CPU_VERSION'] = 7
else:
DEFINES['MOZ_MIN_CPU_VERSION'] = 5
if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY']:
# The Search Activity is mostly independent of Fennec proper, but
# it does depend on Geckoview. Therefore, we build it as a jar
@ -1288,15 +1256,9 @@ if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY']:
'gecko-view.jar',
]
DEFINES['ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME']
FINAL_TARGET_PP_FILES += ['package-name.txt.in']
DEFINES['OBJDIR'] = OBJDIR
DEFINES['TOPOBJDIR'] = TOPOBJDIR
OBJDIR_PP_FILES.mobile.android.base += [
'AndroidManifest.xml.in',
]
gvjar.sources += ['generated/org/mozilla/gecko/' + x for x in [
'IGeckoEditableChild.java',
'IGeckoEditableParent.java',

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

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

После

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

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

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

После

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

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

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

После

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

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

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

После

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

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

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

После

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

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

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

После

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

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

@ -0,0 +1,104 @@
<?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/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="@color/text_and_tabs_tray_grey"
android:minHeight="?actionBarSize"
app:navigationIcon="@drawable/abc_ic_clear_mtrl_alpha"
app:subtitleTextColor="@android:color/white"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:title="@string/bookmark_edit_title"
app:titleTextColor="@android:color/white" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:paddingEnd="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingStart="16dp">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="16dp">
<EditText
android:id="@+id/edit_bookmark_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="start"
android:hint="@string/bookmark_edit_name"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/text_and_tabs_tray_grey"
android:textSize="18sp"
android:focusable="true"
tools:text="Firefox: About your browser" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/edit_bookmark_location_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="16dp">
<EditText
android:id="@+id/edit_bookmark_location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="start"
android:hint="@string/bookmark_edit_location"
android:inputType="textNoSuggestions"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/text_and_tabs_tray_grey"
android:textSize="18sp" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="16dp">
<EditText
android:id="@+id/edit_parent_folder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cursorVisible="false"
android:drawableEnd="@drawable/arrow"
android:drawableRight="@drawable/arrow"
android:drawablePadding="8dp"
android:ellipsize="end"
android:focusable="false"
android:focusableInTouchMode="false"
android:gravity="start"
android:hint="@string/bookmark_parent_folder"
android:inputType="none"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/text_and_tabs_tray_grey"
android:textSize="18sp" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
</LinearLayout>

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

@ -6,10 +6,10 @@
<org.mozilla.gecko.home.BookmarkFolderView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.FolderView"
android:layout_width="match_parent"
android:paddingLeft="0dp"
android:paddingStart="0dp"
android:layout_height="wrap_content"
android:minHeight="@dimen/page_row_height"
android:paddingLeft="16dp"
android:paddingStart="16dp"
android:paddingRight="16dp"
android:paddingEnd="16dp"
android:paddingTop="0dp"
android:paddingBottom="0dp"
android:gravity="center_vertical"/>
android:gravity="center_vertical" />

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

@ -6,5 +6,6 @@
<org.mozilla.gecko.home.TwoLinePageRow xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.BookmarkItemView"
android:layout_width="match_parent"
android:layout_height="@dimen/page_row_height"
android:minHeight="@dimen/page_row_height"/>
android:layout_height="wrap_content"
android:minHeight="@dimen/page_row_height"
android:gravity="center_vertical" />

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

@ -20,6 +20,7 @@
gecko:strip="@drawable/home_tab_menu_strip"
gecko:activeTextColor="@color/placeholder_grey"
gecko:inactiveTextColor="@color/tab_text_color"
gecko:tabsMarginLeft="@dimen/tab_strip_content_start" />
gecko:tabsMarginLeft="@dimen/tab_strip_content_start"
gecko:titlebarFill="true" />
</org.mozilla.gecko.home.HomePager>

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

@ -10,17 +10,18 @@
<ImageView android:id="@+id/icon"
android:src="@drawable/folder_closed"
android:layout_width="24dp"
android:layout_height="18dp"
android:scaleType="fitXY"
android:layout_margin="20dp"/>
android:layout_width="@dimen/favicon_small_size"
android:layout_height="@dimen/favicon_small_size"
android:scaleType="fitCenter" />
<LinearLayout android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:paddingRight="10dp"
android:paddingEnd="10dp"
android:paddingLeft="16dp"
android:paddingStart="16dp"
android:paddingRight="16dp"
android:paddingEnd="16dp"
android:orientation="vertical">
<org.mozilla.gecko.widget.FadedSingleColorTextView
@ -41,4 +42,10 @@
</LinearLayout>
<ImageView android:id="@+id/indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/arrow" />
</merge>

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше