зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound
This commit is contained in:
Коммит
248cf7f04f
10
.cron.yml
10
.cron.yml
|
@ -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(×tampHns);
|
||||
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',
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 20 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 20 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 20 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 20 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 20 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 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>
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче