MozReview-Commit-ID: 7AS5EEH6buZ
This commit is contained in:
Wes Kocher 2017-06-13 18:36:04 -07:00
Родитель ed15672d95 1af6d5cf57
Коммит 09a205279b
178 изменённых файлов: 26810 добавлений и 24169 удалений

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

@ -65,13 +65,3 @@ jobs:
when:
- {hour: 16, minute: 0}
- {hour: 4, minute: 0}
- name: nightly-code-coverage
job:
type: decision-task
treeherder-symbol: Nc
target-tasks-method: nightly_code_coverage
run-on-projects:
- mozilla-central
when:
- {hour: 18, minute: 0}

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

@ -28,6 +28,11 @@ var SidebarUI = {
return this._browser = document.getElementById("sidebar");
},
POSITION_START_PREF: "sidebar.position_start",
DEFAULT_SIDEBAR_ID: "viewBookmarksSidebar",
// lastOpenedId is set in show() but unlike currentID it's not cleared out on hide
// and isn't persisted across windows
lastOpenedId: null,
_box: null,
// The constructor of this label accesses the browser element due to the
@ -63,8 +68,8 @@ var SidebarUI = {
enumerator.getNext();
if (!enumerator.hasMoreElements()) {
document.persist("sidebar-box", "sidebarcommand");
document.persist("sidebar-box", "checked");
document.persist("sidebar-box", "width");
document.persist("sidebar-box", "src");
document.persist("sidebar-title", "value");
}
},
@ -169,27 +174,16 @@ var SidebarUI = {
}
let commandID = sourceUI._box.getAttribute("sidebarcommand");
let commandElem = document.getElementById(commandID);
// dynamically generated sidebars will fail this check, but we still
// consider it adopted.
if (!commandElem) {
if (!document.getElementById(commandID)) {
return true;
}
this._title.setAttribute("value",
sourceUI._title.getAttribute("value"));
this._box.setAttribute("width", sourceUI._box.boxObject.width);
this._show(commandID);
this._box.setAttribute("sidebarcommand", commandID);
// Note: we're setting 'src' on this._box, which is a <vbox>, not on
// the <browser id="sidebar">. This lets us delay the actual load until
// delayedStartup().
this._box.setAttribute("src", sourceUI.browser.getAttribute("src"));
this._setVisibility(true);
commandElem.setAttribute("checked", "true");
this.browser.setAttribute("src", this._box.getAttribute("src"));
return true;
},
@ -222,11 +216,8 @@ var SidebarUI = {
return;
}
let command = document.getElementById(commandID);
if (command) {
this._setVisibility(true);
command.setAttribute("checked", "true");
this.browser.setAttribute("src", this._box.getAttribute("src"));
if (document.getElementById(commandID)) {
this._show(commandID);
} else {
// Remove the |sidebarcommand| attribute, because the element it
// refers to no longer exists, so we should assume this sidebar
@ -272,17 +263,12 @@ var SidebarUI = {
this._title.value = value;
},
/**
* Internal helper to show/hide the box and splitter elements.
*
* @param {bool} visible
*/
_setVisibility(visible) {
this._box.hidden = !visible;
this._splitter.hidden = !visible;
if (visible) {
this.setPosition();
getBroadcasterById(id) {
let sidebarBroadcaster = document.getElementById(id);
if (sidebarBroadcaster && sidebarBroadcaster.localName == "broadcaster") {
return sidebarBroadcaster;
}
return null;
},
/**
@ -293,7 +279,14 @@ var SidebarUI = {
* @param {string} commandID ID of the xul:broadcaster element to use.
* @return {Promise}
*/
toggle(commandID = this.currentID) {
toggle(commandID = this.lastOpenedId) {
// First priority for a default value is this.lastOpenedId which is set during show()
// and not reset in hide(), unlike currentID. If show() hasn't been called or the command
// doesn't exist anymore, then fallback to a default sidebar.
if (!commandID || !this.getBroadcasterById(commandID)) {
commandID = this.DEFAULT_SIDEBAR_ID;
}
if (this.isOpen && commandID == this.currentID) {
this.hide();
return Promise.resolve();
@ -305,12 +298,26 @@ var SidebarUI = {
* Show the sidebar, using the parameters from the specified broadcaster.
* @see SidebarUI note.
*
* This wraps the internal method, including a ping to telemetry.
*
* @param {string} commandID ID of the xul:broadcaster element to use.
*/
show(commandID) {
return this._show(commandID).then(() => {
BrowserUITelemetry.countSidebarEvent(commandID, "show");
});
},
/**
* Implementation for show. Also used internally for sidebars that are shown
* when a window is opened and we don't want to ping telemetry.
*
* @param {string} commandID ID of the xul:broadcaster element to use.
*/
_show(commandID) {
return new Promise((resolve, reject) => {
let sidebarBroadcaster = document.getElementById(commandID);
if (!sidebarBroadcaster || sidebarBroadcaster.localName != "broadcaster") {
let sidebarBroadcaster = this.getBroadcasterById(commandID);
if (!sidebarBroadcaster) {
reject(new Error("Invalid sidebar broadcaster specified: " + commandID));
return;
}
@ -319,14 +326,8 @@ var SidebarUI = {
BrowserUITelemetry.countSidebarEvent(this.currentID, "hide");
}
let broadcasters = document.getElementsByAttribute("group", "sidebar");
let broadcasters = document.querySelectorAll("broadcaster[group=sidebar]");
for (let broadcaster of broadcasters) {
// skip elements that observe sidebar broadcasters and random
// other elements
if (broadcaster.localName != "broadcaster") {
continue;
}
if (broadcaster != sidebarBroadcaster) {
broadcaster.removeAttribute("checked");
} else {
@ -334,29 +335,28 @@ var SidebarUI = {
}
}
this._setVisibility(true);
this._box.hidden = this._splitter.hidden = false;
this.setPosition();
this.hideSwitcherPanel();
this._box.setAttribute("checked", "true");
this._box.setAttribute("sidebarcommand", sidebarBroadcaster.id);
this.lastOpenedId = sidebarBroadcaster.id;
let title = sidebarBroadcaster.getAttribute("sidebartitle");
if (!title) {
title = sidebarBroadcaster.getAttribute("label");
let title = sidebarBroadcaster.getAttribute("sidebartitle") ||
sidebarBroadcaster.getAttribute("label");
// When loading a web page in the sidebar there is no title set on the
// broadcaster, as it is instead set by openWebPanel. Don't clear out
// the title in this case.
if (title) {
this.title = title;
}
this._title.value = title;
let url = sidebarBroadcaster.getAttribute("sidebarurl");
this.browser.setAttribute("src", url); // kick off async load
// We set this attribute here in addition to setting it on the <browser>
// element itself, because the code in SidebarUI.uninit() persists this
// attribute, not the "src" of the <browser id="sidebar">. The reason it
// does that is that we want to delay sidebar load a bit when a browser
// window opens. See delayedStartup() and SidebarUI.startDelayedLoad().
this._box.setAttribute("src", url);
if (this.browser.contentDocument.location.href != url) {
this.browser.addEventListener("load", event => {
@ -380,7 +380,6 @@ var SidebarUI = {
selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
{commandID, isOpen: true}
);
BrowserUITelemetry.countSidebarEvent(commandID, "show");
});
},
@ -411,8 +410,9 @@ var SidebarUI = {
sidebarBroadcaster.removeAttribute("checked");
this._box.setAttribute("sidebarcommand", "");
this._title.value = "";
this._setVisibility(false);
this._box.removeAttribute("checked");
this.title = "";
this._box.hidden = this._splitter.hidden = true;
let selBrowser = gBrowser.selectedBrowser;
selBrowser.focus();

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

@ -18,4 +18,6 @@ skip-if = e10s && (asan || debug) # bug 1347625
[browser_devices_get_user_media_unprompted_access_in_frame.js]
[browser_devices_get_user_media_unprompted_access_tear_off_tab.js]
skip-if = (os == "win" && bits == 64) # win8: bug 1334752
[browser_devices_get_user_media_unprompted_access_queue_request.js]
[browser_webrtc_hooks.js]
[browser_devices_get_user_media_queue_request.js]

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

@ -0,0 +1,158 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const permissionError = "error: NotAllowedError: The request is not allowed " +
"by the user agent or the platform in the current context.";
const badDeviceError =
"error: NotReadableError: Failed to allocate videosource";
var gTests = [
{
desc: "test queueing deny audio behind allow video",
run: async function testQueuingDenyAudioBehindAllowVideo() {
let promise = promisePopupNotificationShown("webRTC-shareDevices");
await promiseRequestDevice(false, true);
await promiseRequestDevice(true, false);
await promise;
promise = promisePopupNotificationShown("webRTC-shareDevices");
checkDeviceSelectors(false, true);
await expectObserverCalled("getUserMedia:request");
let indicator = promiseIndicatorWindow();
await promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
});
await expectObserverCalled("getUserMedia:response:allow");
await expectObserverCalled("recording-device-events");
Assert.deepEqual((await getMediaCaptureState()), {video: true},
"expected camera to be shared");
await indicator;
await checkSharingUI({audio: false, video: true});
await promise;
await expectObserverCalled("getUserMedia:request");
checkDeviceSelectors(true, false);
await promiseMessage(permissionError, () => {
activateSecondaryAction(kActionDeny);
});
await expectObserverCalled("getUserMedia:response:deny");
SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
// close all streams
await closeStream();
}
},
{
desc: "test queueing allow video behind deny audio",
run: async function testQueuingAllowVideoBehindDenyAudio() {
let promise = promisePopupNotificationShown("webRTC-shareDevices");
await promiseRequestDevice(true, false);
await promiseRequestDevice(false, true);
await promise;
promise = promisePopupNotificationShown("webRTC-shareDevices");
await expectObserverCalled("getUserMedia:request");
checkDeviceSelectors(true, false);
await promiseMessage(permissionError, () => {
activateSecondaryAction(kActionDeny);
});
await expectObserverCalled("getUserMedia:response:deny");
await promise;
checkDeviceSelectors(false, true);
await expectObserverCalled("getUserMedia:request");
let indicator = promiseIndicatorWindow();
await promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
});
await expectObserverCalled("getUserMedia:response:allow");
await expectObserverCalled("recording-device-events");
Assert.deepEqual((await getMediaCaptureState()), {video: true},
"expected camera to be shared");
await indicator;
await checkSharingUI({audio: false, video: true});
SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
// close all streams
await closeStream();
}
},
{
desc: "test queueing allow audio behind allow video with error",
run: async function testQueuingAllowAudioBehindAllowVideoWithError() {
let promise = promisePopupNotificationShown("webRTC-shareDevices");
await promiseRequestDevice(false, true, null, null, gBrowser.selectedBrowser, true);
await promiseRequestDevice(true, false);
await promise;
promise = promisePopupNotificationShown("webRTC-shareDevices");
checkDeviceSelectors(false, true);
await expectObserverCalled("getUserMedia:request");
await promiseMessage(badDeviceError, () => {
PopupNotifications.panel.firstChild.button.click();
});
await expectObserverCalled("getUserMedia:response:allow");
await promise;
checkDeviceSelectors(true, false);
await expectObserverCalled("getUserMedia:request");
let indicator = promiseIndicatorWindow();
await promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
});
await expectObserverCalled("getUserMedia:response:allow");
await expectObserverCalled("recording-device-events");
Assert.deepEqual((await getMediaCaptureState()), {audio: true},
"expected microphone to be shared");
await indicator;
await checkSharingUI({audio: true, video: false});
// close all streams
await closeStream();
}
},
{
desc: "test queueing audio+video behind deny audio",
run: async function testQueuingAllowVideoBehindDenyAudio() {
let promise = promisePopupNotificationShown("webRTC-shareDevices");
await promiseRequestDevice(true, false);
await promiseRequestDevice(true, true);
await promise;
await expectObserverCalled("getUserMedia:request");
checkDeviceSelectors(true, false);
promise = promiseSpecificMessageReceived(permissionError, 2);
activateSecondaryAction(kActionDeny);
await promise;
await expectObserverCalled("getUserMedia:request");
await expectObserverCalled("getUserMedia:response:deny", 2);
await expectObserverCalled("recording-window-ended");
SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
}
}
];
add_task(async function test() {
await runTests(gTests);
});

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

@ -0,0 +1,37 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var gTests = [
{
desc: "test queueing allow video behind allow video",
run: async function testQueuingAllowVideoBehindAllowVideo() {
let promise = promisePopupNotificationShown("webRTC-shareDevices");
await promiseRequestDevice(false, true);
await promiseRequestDevice(false, true);
await promise;
checkDeviceSelectors(false, true);
await expectObserverCalled("getUserMedia:request");
let promiseOK = promiseSpecificMessageReceived("ok", 2);
PopupNotifications.panel.firstChild.button.click();
await promiseOK;
await promiseNoPopupNotification("webRTC-shareDevices");
await expectObserverCalled("getUserMedia:request");
await expectObserverCalled("getUserMedia:response:allow", 2);
Assert.deepEqual((await getMediaCaptureState()), {video: true},
"expected camera to be shared");
await expectObserverCalled("recording-device-events", 2);
// close all streams
await closeStream();
}
}
];
add_task(async function test() {
SimpleTest.requestCompleteLog();
await runTests(gTests);
});

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

@ -24,7 +24,7 @@ function message(m) {
var gStreams = [];
function requestDevice(aAudio, aVideo, aShare) {
function requestDevice(aAudio, aVideo, aShare, aBadDevice = false) {
var opts = {video: aVideo, audio: aAudio};
if (aShare) {
opts.video = {
@ -35,6 +35,20 @@ function requestDevice(aAudio, aVideo, aShare) {
opts.fake = true;
}
if (aVideo && aBadDevice) {
opts.video = {
deviceId: "bad device"
}
opts.fake = true;
}
if (aAudio && aBadDevice) {
opts.audio = {
deviceId: "bad device"
}
opts.fake = true;
}
window.navigator.mediaDevices.getUserMedia(opts)
.then(stream => {
gStreams.push(stream);

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

@ -51,11 +51,12 @@ kObservedTopics.forEach(topic => {
Services.obs.addObserver(observer, topic);
});
addMessageListener("Test:ExpectObserverCalled", ({data}) => {
addMessageListener("Test:ExpectObserverCalled", ({ data: { topic, count } }) => {
sendAsyncMessage("Test:ExpectObserverCalled:Reply",
{count: gObservedTopics[data]});
if (data in gObservedTopics)
--gObservedTopics[data];
{count: gObservedTopics[topic]});
if (topic in gObservedTopics) {
gObservedTopics[topic] -= count;
}
});
addMessageListener("Test:ExpectNoObserverCalled", data => {
@ -120,8 +121,18 @@ addMessageListener("Test:WaitForObserverCall", ({data}) => {
}, topic);
});
function messageListener({data}) {
sendAsyncMessage("Test:MessageReceived", data);
}
addMessageListener("Test:WaitForMessage", () => {
content.addEventListener("message", ({data}) => {
sendAsyncMessage("Test:MessageReceived", data);
}, {once: true});
content.addEventListener("message", messageListener, {once: true});
});
addMessageListener("Test:WaitForMultipleMessages", () => {
content.addEventListener("message", messageListener);
});
addMessageListener("Test:StopWaitForMultipleMessages", () => {
content.removeEventListener("message", messageListener);
});

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

@ -202,16 +202,16 @@ function promiseObserverCalled(aTopic) {
});
}
function expectObserverCalled(aTopic) {
function expectObserverCalled(aTopic, aCount = 1) {
return new Promise(resolve => {
let mm = _mm();
mm.addMessageListener("Test:ExpectObserverCalled:Reply",
function listener({data}) {
is(data.count, 1, "expected notification " + aTopic);
is(data.count, aCount, "expected notification " + aTopic);
mm.removeMessageListener("Test:ExpectObserverCalled:Reply", listener);
resolve();
});
mm.sendAsyncMessage("Test:ExpectObserverCalled", aTopic);
mm.sendAsyncMessage("Test:ExpectObserverCalled", {topic: aTopic, count: aCount});
});
}
@ -256,6 +256,24 @@ function promiseMessageReceived() {
});
}
function promiseSpecificMessageReceived(aMessage, aCount = 1) {
return new Promise(resolve => {
let mm = _mm();
let counter = 0;
mm.addMessageListener("Test:MessageReceived", function listener({data}) {
if (data == aMessage) {
counter++;
if (counter == aCount) {
mm.sendAsyncMessage("Test:StopWaitForMultipleMessages");
mm.removeMessageListener("Test:MessageReceived", listener);
resolve(data);
}
}
});
mm.sendAsyncMessage("Test:WaitForMultipleMessages");
});
}
function promiseMessage(aMessage, aAction) {
let promise = new Promise((resolve, reject) => {
promiseMessageReceived(aAction).then(data => {
@ -370,15 +388,16 @@ async function stopSharing(aType = "camera", aShouldKeepSharing = false) {
}
function promiseRequestDevice(aRequestAudio, aRequestVideo, aFrameId, aType,
aBrowser = gBrowser.selectedBrowser) {
aBrowser = gBrowser.selectedBrowser,
aBadDevice = false) {
info("requesting devices");
return ContentTask.spawn(aBrowser,
{aRequestAudio, aRequestVideo, aFrameId, aType},
{aRequestAudio, aRequestVideo, aFrameId, aType, aBadDevice},
async function(args) {
let global = content.wrappedJSObject;
if (args.aFrameId)
global = global.document.getElementById(args.aFrameId).contentWindow;
global.requestDevice(args.aRequestAudio, args.aRequestVideo, args.aType);
global.requestDevice(args.aRequestAudio, args.aRequestVideo, args.aType, args.aBadDevice);
});
}

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

@ -596,21 +596,18 @@ const CustomizableWidgets = [
}
}, {
id: "sidebar-button",
type: "view",
viewId: "PanelUI-sidebar",
tooltiptext: "sidebar-button.tooltiptext2",
onViewShowing(aEvent) {
// Populate the subview with whatever menuitems are in the
// sidebar menu. We skip menu elements, because the menu panel has no way
// of dealing with those right now.
let doc = aEvent.target.ownerDocument;
let menu = doc.getElementById("viewSidebarMenu");
// First clear any existing menuitems then populate. Add it to the
// standard menu first, then copy all sidebar options to the panel.
let sidebarItems = doc.getElementById("PanelUI-sidebarItems");
clearSubview(sidebarItems);
fillSubviewFromMenuItems([...menu.children], sidebarItems);
onCommand(aEvent) {
let win = aEvent.target.ownerGlobal;
win.SidebarUI.toggle();
},
onCreated(aNode) {
// Add an observer so the button is checked while the sidebar is open
let doc = aNode.ownerDocument;
let obnode = doc.createElementNS(kNSXUL, "observes");
obnode.setAttribute("element", "sidebar-box");
obnode.setAttribute("attribute", "checked");
aNode.appendChild(obnode);
}
}, {
id: "social-share-button",

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

@ -125,7 +125,6 @@ skip-if = os == "linux"
[browser_987177_xul_wrapper_updating.js]
[browser_987492_window_api.js]
[browser_987640_charEncoding.js]
[browser_988072_sidebar_events.js]
[browser_989338_saved_placements_not_resaved.js]
[browser_989751_subviewbutton_class.js]
[browser_992747_toggle_noncustomizable_toolbar.js]
@ -165,4 +164,5 @@ tags = fullscreen
[browser_editcontrols_update.js]
subsuite = clipboard
[browser_photon_customization_context_menus.js]
[browser_sidebar_toggle.js]
[browser_remote_tabs_button.js]

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

@ -67,7 +67,6 @@ add_task(async function() {
});
add_task(checkSeparatorInsertion("menuWebDeveloperPopup", "developer-button", "PanelUI-developerItems"));
add_task(checkSeparatorInsertion("viewSidebarMenu", "sidebar-button", "PanelUI-sidebarItems"));
registerCleanupFunction(function() {
for (let el of tempElements) {

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

@ -1,392 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var gSidebarMenu = document.getElementById("viewSidebarMenu");
var gTestSidebarItem = null;
var EVENTS = {
click: 0, command: 0,
onclick: 0, oncommand: 0
};
window.sawEvent = function(event, isattr) {
let type = (isattr ? "on" : "") + event.type
EVENTS[type]++;
};
registerCleanupFunction(() => {
delete window.sawEvent;
// Ensure sidebar is hidden after each test:
if (!document.getElementById("sidebar-box").hidden) {
SidebarUI.hide();
}
});
function checkExpectedEvents(expected) {
for (let type of Object.keys(EVENTS)) {
let count = (type in expected ? expected[type] : 0);
is(EVENTS[type], count, "Should have seen the right number of " + type + " events");
EVENTS[type] = 0;
}
}
function createSidebarItem() {
gTestSidebarItem = document.createElement("menuitem");
gTestSidebarItem.id = "testsidebar";
gTestSidebarItem.setAttribute("label", "Test Sidebar");
gSidebarMenu.insertBefore(gTestSidebarItem, gSidebarMenu.firstChild);
}
function addWidget() {
CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar");
PanelUI.disableSingleSubviewPanelAnimations();
}
function removeWidget() {
CustomizableUI.removeWidgetFromArea("sidebar-button");
PanelUI.enableSingleSubviewPanelAnimations();
}
// Filters out the trailing menuseparators from the sidebar list
function getSidebarList() {
let sidebars = [...gSidebarMenu.children].filter(sidebar => {
if (sidebar.localName == "menuseparator")
return false;
if (sidebar.getAttribute("hidden") == "true")
return false;
return true;
});
return sidebars;
}
function compareElements(original, displayed) {
let attrs = ["label", "key", "disabled", "hidden", "origin", "image", "checked"];
for (let attr of attrs) {
is(displayed.getAttribute(attr), original.getAttribute(attr), "Should have the same " + attr + " attribute");
}
}
function compareList(original, displayed) {
is(displayed.length, original.length, "Should have the same number of children");
for (let i = 0; i < Math.min(original.length, displayed.length); i++) {
compareElements(displayed[i], original[i]);
}
}
var showSidebarPopup = async function() {
let button = document.getElementById("sidebar-button");
let subview = document.getElementById("PanelUI-sidebar");
let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
let subviewShownPromise = subviewShown(subview);
EventUtils.synthesizeMouseAtCenter(button, {});
return Promise.all([subviewShownPromise, popupShownPromise]);
};
// Check the sidebar widget shows the default items
add_task(async function() {
addWidget();
await showSidebarPopup();
let sidebars = getSidebarList();
let displayed = [...document.getElementById("PanelUI-sidebarItems").children];
compareList(sidebars, displayed);
let subview = document.getElementById("PanelUI-sidebar");
let subviewHiddenPromise = subviewHidden(subview);
document.getElementById("customizationui-widget-panel").hidePopup();
await subviewHiddenPromise;
removeWidget();
});
function add_sidebar_task(description, setup, teardown) {
add_task(async function() {
info(description);
createSidebarItem();
addWidget();
await setup();
CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar");
await showSidebarPopup();
let sidebars = getSidebarList();
let displayed = [...document.getElementById("PanelUI-sidebarItems").children];
compareList(sidebars, displayed);
is(displayed[0].label, "Test Sidebar", "Should have the right element at the top");
let subview = document.getElementById("PanelUI-sidebar");
let subviewHiddenPromise = subviewHidden(subview);
EventUtils.synthesizeMouseAtCenter(displayed[0], {});
await subviewHiddenPromise;
await teardown();
gTestSidebarItem.remove();
removeWidget();
});
}
add_sidebar_task(
"Check that a sidebar that uses a command event listener works",
function() {
gTestSidebarItem.addEventListener("command", window.sawEvent);
}, function() {
checkExpectedEvents({ command: 1 });
});
add_sidebar_task(
"Check that a sidebar that uses a click event listener works",
function() {
gTestSidebarItem.addEventListener("click", window.sawEvent);
}, function() {
checkExpectedEvents({ click: 1 });
});
add_sidebar_task(
"Check that a sidebar that uses both click and command event listeners works",
function() {
gTestSidebarItem.addEventListener("command", window.sawEvent);
gTestSidebarItem.addEventListener("click", window.sawEvent);
}, function() {
checkExpectedEvents({ command: 1, click: 1 });
});
add_sidebar_task(
"Check that a sidebar that uses an oncommand attribute works",
function() {
gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)");
}, function() {
checkExpectedEvents({ oncommand: 1 });
});
add_sidebar_task(
"Check that a sidebar that uses an onclick attribute works",
function() {
gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)");
}, function() {
checkExpectedEvents({ onclick: 1 });
});
add_sidebar_task(
"Check that a sidebar that uses both onclick and oncommand attributes works",
function() {
gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)");
gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)");
}, function() {
checkExpectedEvents({ onclick: 1, oncommand: 1 });
});
add_sidebar_task(
"Check that a sidebar that uses an onclick attribute and a command listener works",
function() {
gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)");
gTestSidebarItem.addEventListener("command", window.sawEvent);
}, function() {
checkExpectedEvents({ onclick: 1, command: 1 });
});
add_sidebar_task(
"Check that a sidebar that uses an oncommand attribute and a click listener works",
function() {
gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)");
gTestSidebarItem.addEventListener("click", window.sawEvent);
}, function() {
checkExpectedEvents({ click: 1, oncommand: 1 });
});
add_sidebar_task(
"A sidebar with both onclick attribute and click listener sees only one event :(",
function() {
gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)");
gTestSidebarItem.addEventListener("click", window.sawEvent);
}, function() {
checkExpectedEvents({ onclick: 1 });
});
add_sidebar_task(
"A sidebar with both oncommand attribute and command listener sees only one event :(",
function() {
gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)");
gTestSidebarItem.addEventListener("command", window.sawEvent);
}, function() {
checkExpectedEvents({ oncommand: 1 });
});
add_sidebar_task(
"Check that a sidebar that uses a broadcaster with an oncommand attribute works",
function() {
let broadcaster = document.createElement("broadcaster");
broadcaster.setAttribute("id", "testbroadcaster");
broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)");
broadcaster.setAttribute("label", "Test Sidebar");
document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
gTestSidebarItem.setAttribute("observes", "testbroadcaster");
}, function() {
checkExpectedEvents({ oncommand: 1 });
document.getElementById("testbroadcaster").remove();
});
add_sidebar_task(
"Check that a sidebar that uses a broadcaster with an onclick attribute works",
function() {
let broadcaster = document.createElement("broadcaster");
broadcaster.setAttribute("id", "testbroadcaster");
broadcaster.setAttribute("onclick", "window.sawEvent(event, true)");
broadcaster.setAttribute("label", "Test Sidebar");
document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
gTestSidebarItem.setAttribute("observes", "testbroadcaster");
}, function() {
checkExpectedEvents({ onclick: 1 });
document.getElementById("testbroadcaster").remove();
});
add_sidebar_task(
"Check that a sidebar that uses a broadcaster with both onclick and oncommand attributes works",
function() {
let broadcaster = document.createElement("broadcaster");
broadcaster.setAttribute("id", "testbroadcaster");
broadcaster.setAttribute("onclick", "window.sawEvent(event, true)");
broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)");
broadcaster.setAttribute("label", "Test Sidebar");
document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
gTestSidebarItem.setAttribute("observes", "testbroadcaster");
}, function() {
checkExpectedEvents({ onclick: 1, oncommand: 1 });
document.getElementById("testbroadcaster").remove();
});
add_sidebar_task(
"Check that a sidebar with a click listener and a broadcaster with an oncommand attribute works",
function() {
let broadcaster = document.createElement("broadcaster");
broadcaster.setAttribute("id", "testbroadcaster");
broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)");
broadcaster.setAttribute("label", "Test Sidebar");
document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
gTestSidebarItem.setAttribute("observes", "testbroadcaster");
gTestSidebarItem.addEventListener("click", window.sawEvent);
}, function() {
checkExpectedEvents({ click: 1, oncommand: 1 });
document.getElementById("testbroadcaster").remove();
});
add_sidebar_task(
"Check that a sidebar with a command listener and a broadcaster with an onclick attribute works",
function() {
let broadcaster = document.createElement("broadcaster");
broadcaster.setAttribute("id", "testbroadcaster");
broadcaster.setAttribute("onclick", "window.sawEvent(event, true)");
broadcaster.setAttribute("label", "Test Sidebar");
document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
gTestSidebarItem.setAttribute("observes", "testbroadcaster");
gTestSidebarItem.addEventListener("command", window.sawEvent);
}, function() {
checkExpectedEvents({ onclick: 1, command: 1 });
document.getElementById("testbroadcaster").remove();
});
add_sidebar_task(
"Check that a sidebar with a click listener and a broadcaster with an onclick " +
"attribute only sees one event :(",
function() {
let broadcaster = document.createElement("broadcaster");
broadcaster.setAttribute("id", "testbroadcaster");
broadcaster.setAttribute("onclick", "window.sawEvent(event, true)");
broadcaster.setAttribute("label", "Test Sidebar");
document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
gTestSidebarItem.setAttribute("observes", "testbroadcaster");
gTestSidebarItem.addEventListener("click", window.sawEvent);
}, function() {
checkExpectedEvents({ onclick: 1 });
document.getElementById("testbroadcaster").remove();
});
add_sidebar_task(
"Check that a sidebar with a command listener and a broadcaster with an oncommand " +
"attribute only sees one event :(",
function() {
let broadcaster = document.createElement("broadcaster");
broadcaster.setAttribute("id", "testbroadcaster");
broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)");
broadcaster.setAttribute("label", "Test Sidebar");
document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
gTestSidebarItem.setAttribute("observes", "testbroadcaster");
gTestSidebarItem.addEventListener("command", window.sawEvent);
}, function() {
checkExpectedEvents({ oncommand: 1 });
document.getElementById("testbroadcaster").remove();
});
add_sidebar_task(
"Check that a sidebar that uses a command element with a command event listener works",
function() {
let command = document.createElement("command");
command.setAttribute("id", "testcommand");
document.getElementById("mainCommandSet").appendChild(command);
command.addEventListener("command", window.sawEvent);
gTestSidebarItem.setAttribute("command", "testcommand");
}, function() {
checkExpectedEvents({ command: 1 });
document.getElementById("testcommand").remove();
});
add_sidebar_task(
"Check that a sidebar that uses a command element with an oncommand attribute works",
function() {
let command = document.createElement("command");
command.setAttribute("id", "testcommand");
command.setAttribute("oncommand", "window.sawEvent(event, true)");
document.getElementById("mainCommandSet").appendChild(command);
gTestSidebarItem.setAttribute("command", "testcommand");
}, function() {
checkExpectedEvents({ oncommand: 1 });
document.getElementById("testcommand").remove();
});
add_sidebar_task("Check that a sidebar that uses a command element with a " +
"command event listener and oncommand attribute works",
function() {
let command = document.createElement("command");
command.setAttribute("id", "testcommand");
command.setAttribute("oncommand", "window.sawEvent(event, true)");
document.getElementById("mainCommandSet").appendChild(command);
command.addEventListener("command", window.sawEvent);
gTestSidebarItem.setAttribute("command", "testcommand");
}, function() {
checkExpectedEvents({ command: 1, oncommand: 1 });
document.getElementById("testcommand").remove();
});
add_sidebar_task(
"A sidebar with a command element will still see click events",
function() {
let command = document.createElement("command");
command.setAttribute("id", "testcommand");
command.setAttribute("oncommand", "window.sawEvent(event, true)");
document.getElementById("mainCommandSet").appendChild(command);
command.addEventListener("command", window.sawEvent);
gTestSidebarItem.setAttribute("command", "testcommand");
gTestSidebarItem.addEventListener("click", window.sawEvent);
}, function() {
checkExpectedEvents({ click: 1, command: 1, oncommand: 1 });
document.getElementById("testcommand").remove();
});

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

@ -57,7 +57,6 @@ add_task(async function() {
});
add_task(checkSubviewButtonClass("menuWebDeveloperPopup", "developer-button", "PanelUI-developerItems"));
add_task(checkSubviewButtonClass("viewSidebarMenu", "sidebar-button", "PanelUI-sidebarItems"));
registerCleanupFunction(function() {
tempElement.classList.remove(kCustomClass)

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

@ -0,0 +1,43 @@
/* 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/. */
"use strict";
registerCleanupFunction(async function() {
await resetCustomization();
// Ensure sidebar is hidden after each test:
if (!document.getElementById("sidebar-box").hidden) {
SidebarUI.hide();
}
});
var showSidebar = async function() {
let button = document.getElementById("sidebar-button");
let sidebarFocusedPromise = BrowserTestUtils.waitForEvent(document, "SidebarFocused");
EventUtils.synthesizeMouseAtCenter(button, {});
await sidebarFocusedPromise;
ok(SidebarUI.isOpen, "Sidebar is opened");
ok(button.hasAttribute("checked"), "Toolbar button is checked");
};
var hideSidebar = async function() {
let button = document.getElementById("sidebar-button");
EventUtils.synthesizeMouseAtCenter(button, {});
ok(!SidebarUI.isOpen, "Sidebar is closed");
ok(!button.hasAttribute("checked"), "Toolbar button isn't checked");
};
// Check the sidebar widget shows the default items
add_task(async function() {
CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar");
await showSidebar();
is(SidebarUI.currentID, "viewBookmarksSidebar", "Default sidebar selected");
await SidebarUI.show("viewHistorySidebar");
await hideSidebar();
await showSidebar();
is(SidebarUI.currentID, "viewHistorySidebar", "Selected sidebar remembered");
});

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

@ -13,8 +13,8 @@
* and the implementation of the `devtools_page`.
*/
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
"resource://devtools/client/framework/gDevTools.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DevToolsShim",
"chrome://devtools-shim/content/DevToolsShim.jsm");
Cu.import("resource://gre/modules/ExtensionParent.jsm");
@ -142,7 +142,7 @@ class DevToolsPage extends HiddenExtensionPage {
extensions.emit("extension-browser-inserted", this.browser, {
devtoolsToolboxInfo: {
inspectedWindowTabId: getTargetTabIdForToolbox(this.toolbox),
themeName: gDevTools.getTheme(),
themeName: DevToolsShim.getTheme(),
},
});
@ -220,7 +220,7 @@ class DevToolsPageDefinition {
// If this is the first DevToolsPage, subscribe to the theme-changed event
if (this.devtoolsPageForTarget.size === 0) {
gDevTools.on("theme-changed", this.onThemeChanged);
DevToolsShim.on("theme-changed", this.onThemeChanged);
}
this.devtoolsPageForTarget.set(toolbox.target, devtoolsPage);
@ -240,7 +240,7 @@ class DevToolsPageDefinition {
// If this was the last DevToolsPage, unsubscribe from the theme-changed event
if (this.devtoolsPageForTarget.size === 0) {
gDevTools.off("theme-changed", this.onThemeChanged);
DevToolsShim.off("theme-changed", this.onThemeChanged);
}
}
}
@ -272,7 +272,7 @@ initDevTools = function() {
/* eslint-disable mozilla/balanced-listeners */
// Create a devtools page context for a new opened toolbox,
// based on the registered devtools_page definitions.
gDevTools.on("toolbox-created", (evt, toolbox) => {
DevToolsShim.on("toolbox-created", (evt, toolbox) => {
if (!toolbox.target.isLocalTab) {
// Only local tabs are currently supported (See Bug 1304378 for additional details
// related to remote targets support).
@ -294,7 +294,7 @@ initDevTools = function() {
// Destroy a devtools page context for a destroyed toolbox,
// based on the registered devtools_page definitions.
gDevTools.on("toolbox-destroy", (evt, target) => {
DevToolsShim.on("toolbox-destroy", (evt, target) => {
if (!target.isLocalTab) {
// Only local tabs are currently supported (See Bug 1304378 for additional details
// related to remote targets support).

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

@ -2,10 +2,8 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
"resource://devtools/client/framework/gDevTools.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
"resource://devtools/shared/Loader.jsm");
const {DevToolsShim} = Cu.import("chrome://devtools-shim/content/DevToolsShim.jsm", {});
const {gDevTools} = DevToolsShim;
/**
* this test file ensures that:
@ -91,7 +89,7 @@ add_task(async function test_devtools_inspectedWindow_tabId() {
let backgroundPageCurrentTabId = await extension.awaitMessage("current-tab-id");
let target = devtools.TargetFactory.forTab(tab);
let target = gDevTools.getTargetForTab(tab);
await gDevTools.showToolbox(target, "webconsole");
info("developer toolbox opened");
@ -159,7 +157,7 @@ add_task(async function test_devtools_inspectedWindow_eval() {
await extension.startup();
let target = devtools.TargetFactory.forTab(tab);
let target = gDevTools.getTargetForTab(tab);
await gDevTools.showToolbox(target, "webconsole");
info("developer toolbox opened");

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

@ -6,10 +6,8 @@
// on debug test slave, it takes about 50s to run the test.
requestLongerTimeout(4);
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
"resource://devtools/client/framework/gDevTools.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
"resource://devtools/shared/Loader.jsm");
const {DevToolsShim} = Cu.import("chrome://devtools-shim/content/DevToolsShim.jsm", {});
const {gDevTools} = DevToolsShim;
// Small helper which provides the common steps to the following reload test cases.
async function runReloadTestCase({urlParams, background, devtoolsPage, testCase}) {
@ -39,7 +37,7 @@ async function runReloadTestCase({urlParams, background, devtoolsPage, testCase}
await extension.startup();
let target = devtools.TargetFactory.forTab(tab);
let target = gDevTools.getTargetForTab(tab);
await gDevTools.showToolbox(target, "webconsole");
info("developer toolbox opened");

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

@ -2,10 +2,8 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
"resource://devtools/shared/Loader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
"resource://devtools/client/framework/gDevTools.jsm");
const {DevToolsShim} = Cu.import("chrome://devtools-shim/content/DevToolsShim.jsm", {});
const {gDevTools} = DevToolsShim;
add_task(async function test_devtools_network_on_navigated() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
@ -65,7 +63,7 @@ add_task(async function test_devtools_network_on_navigated() {
await extension.startup();
await extension.awaitMessage("ready");
let target = devtools.TargetFactory.forTab(tab);
let target = gDevTools.getTargetForTab(tab);
await gDevTools.showToolbox(target, "webconsole");
info("Developer toolbox opened.");

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

@ -2,10 +2,8 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
"resource://devtools/client/framework/gDevTools.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
"resource://devtools/shared/Loader.jsm");
const {DevToolsShim} = Cu.import("chrome://devtools-shim/content/DevToolsShim.jsm", {});
const {gDevTools} = DevToolsShim;
/**
* This test file ensures that:
@ -67,7 +65,7 @@ add_task(async function test_devtools_page_runtime_api_messaging() {
await extension.startup();
let target = devtools.TargetFactory.forTab(tab);
let target = gDevTools.getTargetForTab(tab);
await gDevTools.showToolbox(target, "webconsole");
info("developer toolbox opened");

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

@ -2,13 +2,12 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
"resource://devtools/shared/Loader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
"resource://devtools/client/framework/gDevTools.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
const {DevToolsShim} = Cu.import("chrome://devtools-shim/content/DevToolsShim.jsm", {});
const {gDevTools} = DevToolsShim;
const DEVTOOLS_THEME_PREF = "devtools.theme";
/**
@ -76,7 +75,7 @@ add_task(async function test_theme_name_no_panel() {
await extension.startup();
let target = devtools.TargetFactory.forTab(tab);
let target = gDevTools.getTargetForTab(tab);
await gDevTools.showToolbox(target, "webconsole");
info("developer toolbox opened");
@ -195,7 +194,7 @@ add_task(async function test_devtools_page_panels_create() {
await extension.startup();
let target = devtools.TargetFactory.forTab(tab);
let target = gDevTools.getTargetForTab(tab);
const toolbox = await gDevTools.showToolbox(target, "webconsole");
info("developer toolbox opened");

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

@ -1,4 +1,4 @@
<?xml version="1.0"?> <!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- -->
<?xml version="1.0"?> <!-- -*- Mode: SGML; 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/. -->
@ -17,9 +17,9 @@
onload="init();"
onunload="SidebarUtils.setMouseoverURL('');">
<script type="application/javascript"
<script type="application/javascript"
src="chrome://browser/content/bookmarks/sidebarUtils.js"/>
<script type="application/javascript"
<script type="application/javascript"
src="chrome://browser/content/bookmarks/bookmarksPanel.js"/>
<commandset id="placesCommands"/>
@ -30,9 +30,8 @@
<tooltip id="bhTooltip"/>
<hbox id="sidebar-search-container" align="center">
<label id="sidebar-search-label"
value="&search.label;" accesskey="&search.accesskey;" control="search-box"/>
<textbox id="search-box" flex="1" type="search" class="compact"
<textbox id="search-box" flex="1" type="search"
placeholder="&search.placeholder;"
aria-controls="bookmarks-view"
oncommand="searchBookmarks(this.value);"/>
</hbox>

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

@ -24,9 +24,9 @@
onload="HistorySidebarInit();"
onunload="SidebarUtils.setMouseoverURL('');">
<script type="application/javascript"
<script type="application/javascript"
src="chrome://browser/content/bookmarks/sidebarUtils.js"/>
<script type="application/javascript"
<script type="application/javascript"
src="chrome://browser/content/places/history-panel.js"/>
<commandset id="editMenuCommands"/>
@ -45,31 +45,29 @@
<tooltip id="bhTooltip"/>
<hbox id="sidebar-search-container" align="center">
<label id="sidebar-search-label"
value="&find.label;" accesskey="&find.accesskey;"
control="search-box"/>
<textbox id="search-box" flex="1" type="search" class="compact"
<textbox id="search-box" flex="1" type="search"
placeholder="&search.placeholder;"
aria-controls="historyTree"
oncommand="searchHistory(this.value);"/>
<button id="viewButton" style="min-width:0px !important;" type="menu"
label="&view.label;" accesskey="&view.accesskey;" selectedsort="day"
persist="selectedsort">
<menupopup>
<menuitem id="bydayandsite" label="&byDayAndSite.label;"
<menuitem id="bydayandsite" label="&byDayAndSite.label;"
accesskey="&byDayAndSite.accesskey;" type="radio"
oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'dayandsite'); GroupBy('dayandsite');"/>
<menuitem id="bysite" label="&bySite.label;"
<menuitem id="bysite" label="&bySite.label;"
accesskey="&bySite.accesskey;" type="radio"
oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'site'); GroupBy('site');"/>
<menuitem id="byday" label="&byDate.label;"
<menuitem id="byday" label="&byDate.label;"
accesskey="&byDate.accesskey;"
type="radio"
oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'day'); GroupBy('day');"/>
<menuitem id="byvisited" label="&byMostVisited.label;"
<menuitem id="byvisited" label="&byMostVisited.label;"
accesskey="&byMostVisited.accesskey;"
type="radio"
oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'visited'); GroupBy('visited');"/>
<menuitem id="bylastvisited" label="&byLastVisited.label;"
<menuitem id="bylastvisited" label="&byLastVisited.label;"
accesskey="&byLastVisited.accesskey;"
type="radio"
oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'lastvisited'); GroupBy('lastvisited');"/>

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

@ -7,7 +7,7 @@ browser.jar:
# Provide another URI for the bookmarkProperties dialog so we can persist the
# attributes separately
content/browser/places/bookmarkProperties2.xul (content/bookmarkProperties.xul)
* content/browser/places/places.xul (content/places.xul)
* content/browser/places/places.xul (content/places.xul)
content/browser/places/places.js (content/places.js)
content/browser/places/places.css (content/places.css)
content/browser/places/organizer.css (content/organizer.css)
@ -19,9 +19,9 @@ browser.jar:
content/browser/places/controller.js (content/controller.js)
content/browser/places/treeView.js (content/treeView.js)
content/browser/places/browserPlacesViews.js (content/browserPlacesViews.js)
# keep the Places version of the history sidebar at history/history-panel.xul
# keep the Places version of the history sidebar at history/history-panel.xul
# to prevent having to worry about between versions of the browser
* content/browser/history/history-panel.xul (content/history-panel.xul)
* content/browser/history/history-panel.xul (content/history-panel.xul)
content/browser/places/history-panel.js (content/history-panel.js)
# ditto for the bookmarks sidebar
content/browser/bookmarks/bookmarksPanel.xul (content/bookmarksPanel.xul)

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

@ -14,4 +14,3 @@ export MOZ_PACKAGE_JSSHELL=1
export MOZ_PKG_SPECIAL=asan
. "$topsrcdir/build/mozconfig.common.override"
. "$topsrcdir/build/mozconfig.cache"

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

@ -14,4 +14,3 @@ export MOZ_PACKAGE_JSSHELL=1
export MOZ_PKG_SPECIAL=asan
. "$topsrcdir/build/mozconfig.common.override"
. "$topsrcdir/build/mozconfig.cache"

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

@ -36,7 +36,10 @@ const actionTypes = [
"PLACES_HISTORY_CLEARED",
"PLACES_LINK_BLOCKED",
"PLACES_LINK_DELETED",
"PREFS_INITIAL_VALUES",
"PREF_CHANGED",
"SCREENSHOT_UPDATED",
"SET_PREF",
"TELEMETRY_PERFORMANCE_EVENT",
"TELEMETRY_UNDESIRED_EVENT",
"TELEMETRY_USER_EVENT",
@ -160,6 +163,11 @@ function PerfEvent(data, importContext = globalImportContext) {
return importContext === UI_CODE ? SendToMain(action) : action;
}
function SetPref(name, value, importContext = globalImportContext) {
const action = {type: actionTypes.SET_PREF, data: {name, value}};
return importContext === UI_CODE ? SendToMain(action) : action;
}
this.actionTypes = actionTypes;
this.actionCreators = {
@ -168,7 +176,8 @@ this.actionCreators = {
UndesiredEvent,
PerfEvent,
SendToContent,
SendToMain
SendToMain,
SetPref
};
// These are helpers to test for certain kinds of actions

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

@ -21,6 +21,10 @@ const INITIAL_STATE = {
initialized: false,
// The history (and possibly default) links
rows: []
},
Prefs: {
initialized: false,
values: {}
}
};
@ -91,7 +95,21 @@ function TopSites(prevState = INITIAL_STATE.TopSites, action) {
}
}
function Prefs(prevState = INITIAL_STATE.Prefs, action) {
let newValues;
switch (action.type) {
case at.PREFS_INITIAL_VALUES:
return Object.assign({}, prevState, {initialized: true, values: action.data});
case at.PREF_CHANGED:
newValues = Object.assign({}, prevState.values);
newValues[action.data.name] = action.data.value;
return Object.assign({}, prevState, {values: newValues});
default:
return prevState;
}
}
this.INITIAL_STATE = INITIAL_STATE;
this.reducers = {TopSites, App};
this.reducers = {TopSites, App, Prefs};
this.EXPORTED_SYMBOLS = ["reducers", "INITIAL_STATE"];

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

@ -63,7 +63,7 @@
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 15);
/******/ return __webpack_require__(__webpack_require__.s = 17);
/******/ })
/************************************************************************/
/******/ ([
@ -97,7 +97,7 @@ const globalImportContext = typeof Window === "undefined" ? BACKGROUND_PROCESS :
// Export for tests
const actionTypes = ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "INIT", "LOCALE_UPDATED", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_UNLOAD", "NEW_TAB_VISIBLE", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "SCREENSHOT_UPDATED", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_UPDATED", "UNINIT"
const actionTypes = ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "INIT", "LOCALE_UPDATED", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_UNLOAD", "NEW_TAB_VISIBLE", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SCREENSHOT_UPDATED", "SET_PREF", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_UPDATED", "UNINIT"
// The line below creates an object like this:
// {
// INIT: "INIT",
@ -222,13 +222,21 @@ function PerfEvent(data) {
return importContext === UI_CODE ? SendToMain(action) : action;
}
function SetPref(name, value) {
let importContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : globalImportContext;
const action = { type: actionTypes.SET_PREF, data: { name, value } };
return importContext === UI_CODE ? SendToMain(action) : action;
}
var actionCreators = {
BroadcastToContent,
UserEvent,
UndesiredEvent,
PerfEvent,
SendToContent,
SendToMain
SendToMain,
SetPref
};
// These are helpers to test for certain kinds of actions
@ -304,8 +312,9 @@ var _require2 = __webpack_require__(3);
const addLocaleData = _require2.addLocaleData,
IntlProvider = _require2.IntlProvider;
const TopSites = __webpack_require__(12);
const Search = __webpack_require__(11);
const TopSites = __webpack_require__(13);
const Search = __webpack_require__(12);
const PreferencesPane = __webpack_require__(11);
// Locales that should be displayed RTL
const RTL_LIST = ["ar", "he", "fa", "ur"];
@ -342,10 +351,13 @@ class Base extends React.Component {
}
render() {
var _props$App = this.props.App;
let locale = _props$App.locale,
strings = _props$App.strings,
initialized = _props$App.initialized;
const props = this.props;
var _props$App = props.App;
const locale = _props$App.locale,
strings = _props$App.strings,
initialized = _props$App.initialized;
const prefs = props.Prefs.values;
if (!initialized) {
return null;
@ -360,15 +372,16 @@ class Base extends React.Component {
React.createElement(
"main",
null,
React.createElement(Search, null),
React.createElement(TopSites, null)
)
prefs.showSearch && React.createElement(Search, null),
prefs.showTopSites && React.createElement(TopSites, null)
),
React.createElement(PreferencesPane, null)
)
);
}
}
module.exports = connect(state => ({ App: state.App }))(Base);
module.exports = connect(state => ({ App: state.App, Prefs: state.Prefs }))(Base);
/***/ }),
/* 5 */
@ -442,7 +455,7 @@ module.exports = class DetectUserSessionStart {
/* eslint-env mozilla/frame-script */
var _require = __webpack_require__(14);
var _require = __webpack_require__(16);
const createStore = _require.createStore,
combineReducers = _require.combineReducers,
@ -544,6 +557,10 @@ const INITIAL_STATE = {
initialized: false,
// The history (and possibly default) links
rows: []
},
Prefs: {
initialized: false,
values: {}
}
};
@ -628,7 +645,24 @@ function TopSites() {
}
}
var reducers = { TopSites, App };
function Prefs() {
let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Prefs;
let action = arguments[1];
let newValues;
switch (action.type) {
case at.PREFS_INITIAL_VALUES:
return Object.assign({}, prevState, { initialized: true, values: action.data });
case at.PREF_CHANGED:
newValues = Object.assign({}, prevState.values);
newValues[action.data.name] = action.data.value;
return Object.assign({}, prevState, { values: newValues });
default:
return prevState;
}
}
var reducers = { TopSites, App, Prefs };
module.exports = {
reducers,
INITIAL_STATE
@ -844,6 +878,134 @@ module.exports._unconnected = LinkMenu;
/* 11 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const React = __webpack_require__(0);
var _require = __webpack_require__(2);
const connect = _require.connect;
var _require2 = __webpack_require__(3);
const injectIntl = _require2.injectIntl,
FormattedMessage = _require2.FormattedMessage;
const classNames = __webpack_require__(15);
var _require3 = __webpack_require__(1);
const ac = _require3.actionCreators;
const PreferencesInput = props => React.createElement(
"section",
null,
React.createElement("input", { type: "checkbox", id: props.prefName, name: props.prefName, checked: props.value, onChange: props.onChange, className: props.className }),
React.createElement(
"label",
{ htmlFor: props.prefName },
React.createElement(FormattedMessage, { id: props.titleStringId })
),
props.descStringId && React.createElement(
"p",
{ className: "prefs-input-description" },
React.createElement(FormattedMessage, { id: props.descStringId })
)
);
class PreferencesPane extends React.Component {
constructor(props) {
super(props);
this.state = { visible: false };
this.handleClickOutside = this.handleClickOutside.bind(this);
this.handleChange = this.handleChange.bind(this);
this.togglePane = this.togglePane.bind(this);
}
componentDidMount() {
document.addEventListener("click", this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener("click", this.handleClickOutside);
}
handleClickOutside(event) {
// if we are showing the sidebar and there is a click outside, close it.
if (this.state.visible && !this.refs.wrapper.contains(event.target)) {
this.togglePane();
}
}
handleChange(event) {
const target = event.target;
this.props.dispatch(ac.SetPref(target.name, target.checked));
}
togglePane() {
this.setState({ visible: !this.state.visible });
const event = this.state.visible ? "CLOSE_NEWTAB_PREFS" : "OPEN_NEWTAB_PREFS";
this.props.dispatch(ac.UserEvent({ event }));
}
render() {
const props = this.props;
const prefs = props.Prefs.values;
const isVisible = this.state.visible;
return React.createElement(
"div",
{ className: "prefs-pane-wrapper", ref: "wrapper" },
React.createElement(
"div",
{ className: "prefs-pane-button" },
React.createElement("button", {
className: classNames("prefs-button icon", isVisible ? "icon-dismiss" : "icon-settings"),
title: props.intl.formatMessage({ id: isVisible ? "settings_pane_done_button" : "settings_pane_button_label" }),
onClick: this.togglePane })
),
React.createElement(
"div",
{ className: "prefs-pane" },
React.createElement(
"div",
{ className: classNames("sidebar", { hidden: !isVisible }) },
React.createElement(
"div",
{ className: "prefs-modal-inner-wrapper" },
React.createElement(
"h1",
null,
React.createElement(FormattedMessage, { id: "settings_pane_header" })
),
React.createElement(
"p",
null,
React.createElement(FormattedMessage, { id: "settings_pane_body" })
),
React.createElement(PreferencesInput, { className: "showSearch", prefName: "showSearch", value: prefs.showSearch, onChange: this.handleChange,
titleStringId: "settings_pane_search_header", descStringId: "settings_pane_search_body" }),
React.createElement(PreferencesInput, { className: "showTopSites", prefName: "showTopSites", value: prefs.showTopSites, onChange: this.handleChange,
titleStringId: "settings_pane_topsites_header", descStringId: "settings_pane_topsites_body" })
),
React.createElement(
"section",
{ className: "actions" },
React.createElement(
"button",
{ className: "done", onClick: this.togglePane },
React.createElement(FormattedMessage, { id: "settings_pane_done_button" })
)
)
)
)
);
}
}
module.exports = connect(state => ({ Prefs: state.Prefs }))(injectIntl(PreferencesPane));
module.exports.PreferencesPane = PreferencesPane;
module.exports.PreferencesInput = PreferencesInput;
/***/ }),
/* 12 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* globals ContentSearchUIController */
@ -882,7 +1044,7 @@ class Search extends React.Component {
}
onInputMount(input) {
if (input) {
this.controller = new ContentSearchUIController(input, input.parentNode, "newtab", "activity");
this.controller = new ContentSearchUIController(input, input.parentNode, "activity", "newtab");
addEventListener("ContentSearchClient", this);
} else {
this.controller = null;
@ -930,7 +1092,7 @@ module.exports = connect()(injectIntl(Search));
module.exports._unconnected = Search;
/***/ }),
/* 12 */
/* 13 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -946,7 +1108,7 @@ var _require2 = __webpack_require__(3);
const FormattedMessage = _require2.FormattedMessage;
const shortURL = __webpack_require__(13);
const shortURL = __webpack_require__(14);
const LinkMenu = __webpack_require__(10);
var _require3 = __webpack_require__(1);
@ -1051,7 +1213,7 @@ module.exports._unconnected = TopSites;
module.exports.TopSite = TopSite;
/***/ }),
/* 13 */
/* 14 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -1086,13 +1248,68 @@ module.exports = function shortURL(link) {
};
/***/ }),
/* 14 */
/* 15 */
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
Copyright (c) 2016 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/* global define */
(function () {
'use strict';
var hasOwn = {}.hasOwnProperty;
function classNames () {
var classes = [];
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (!arg) continue;
var argType = typeof arg;
if (argType === 'string' || argType === 'number') {
classes.push(arg);
} else if (Array.isArray(arg)) {
classes.push(classNames.apply(null, arg));
} else if (argType === 'object') {
for (var key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
}
}
return classes.join(' ');
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = classNames;
} else if (true) {
// register as 'classnames', consistent with npm package name
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function () {
return classNames;
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else {
window.classNames = classNames;
}
}());
/***/ }),
/* 16 */
/***/ (function(module, exports) {
module.exports = Redux;
/***/ }),
/* 15 */
/* 17 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";

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

@ -42,6 +42,8 @@ input {
background-image: url("assets/glyph-newWindow-16.svg"); }
.icon.icon-new-window-private {
background-image: url("assets/glyph-newWindow-private-16.svg"); }
.icon.icon-settings {
background-image: url("assets/glyph-settings-16.svg"); }
html,
body,
@ -340,3 +342,115 @@ main {
color: #383E49; }
.context-menu > ul > li > a:hover:hover, .context-menu > ul > li > a:hover:focus, .context-menu > ul > li > a:focus:hover, .context-menu > ul > li > a:focus:focus {
color: #FFF; }
.prefs-pane {
font-size: 13px; }
.prefs-pane .sidebar {
background: #FFF;
border-left: solid 1px rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.08);
min-height: 100%;
offset-inline-end: 0;
padding: 40px;
position: fixed;
top: 0;
transition: 0.1s cubic-bezier(0, 0, 0, 1);
transition-property: left, right;
width: 400px;
z-index: 12000; }
.prefs-pane .sidebar.hidden {
offset-inline-end: -400px; }
.prefs-pane .sidebar h1 {
border-bottom: solid 1px rgba(0, 0, 0, 0.1);
font-size: 24px;
margin: 0;
padding: 20px 0; }
.prefs-pane .prefs-modal-inner-wrapper section {
margin: 20px 0; }
.prefs-pane .prefs-modal-inner-wrapper section p {
margin: 5px 0 5px 30px; }
.prefs-pane .prefs-modal-inner-wrapper section label {
position: relative; }
.prefs-pane .prefs-modal-inner-wrapper section label input {
offset-inline-start: -30px;
position: absolute;
top: 0; }
.prefs-pane .prefs-modal-inner-wrapper section > label {
font-size: 16px;
font-weight: bold; }
.prefs-pane .prefs-modal-inner-wrapper section .options {
background: #FBFBFB;
border: solid 1px rgba(0, 0, 0, 0.1);
border-radius: 3px;
margin: 15px 0;
margin-inline-start: 30px;
padding: 10px; }
.prefs-pane .prefs-modal-inner-wrapper section .options label {
background-position: 35px center;
background-repeat: no-repeat;
display: inline-block;
font-weight: normal;
height: 21px;
line-height: 21px;
padding-inline-start: 63px;
width: 100%; }
.prefs-pane .prefs-modal-inner-wrapper section .options label:dir(rtl) {
background-position: 217px center; }
.prefs-pane .prefs-modal-inner-wrapper section.disabled .options {
opacity: 0.5; }
.prefs-pane .actions {
padding: 15px 0; }
.prefs-pane [type='checkbox']:not(:checked),
.prefs-pane [type='checkbox']:checked {
offset-inline-start: -9999px;
position: absolute; }
.prefs-pane [type='checkbox']:not(:checked) + label,
.prefs-pane [type='checkbox']:checked + label {
cursor: pointer;
padding: 0 30px;
position: relative; }
.prefs-pane [type='checkbox']:not(:checked) + label::before,
.prefs-pane [type='checkbox']:checked + label::before {
background: #FFF;
border: 1px solid #C1C1C1;
border-radius: 3px;
content: '';
height: 21px;
offset-inline-start: 0;
position: absolute;
top: 0;
width: 21px; }
.prefs-pane [type='checkbox']:not(:checked) + label::after,
.prefs-pane [type='checkbox']:checked + label::after {
background: url("chrome://global/skin/in-content/check.svg#check") no-repeat center center;
content: '';
height: 21px;
offset-inline-start: 0;
position: absolute;
top: 0;
width: 21px; }
.prefs-pane [type='checkbox']:not(:checked) + label::after {
opacity: 0; }
.prefs-pane [type='checkbox']:checked + label::after {
opacity: 1; }
.prefs-pane [type='checkbox'] + label:hover::before {
border: 1px solid #1691D2; }
.prefs-pane [type='checkbox']:checked:focus + label::before,
.prefs-pane [type='checkbox']:not(:checked):focus + label::before {
border: 1px dotted #1691D2; }
.prefs-pane-button button {
background-color: transparent;
border: 0;
cursor: pointer;
opacity: 0.7;
padding: 15px;
position: fixed;
right: 15px;
top: 15px;
z-index: 12001; }
.prefs-pane-button button:hover {
background-color: #EBEBEB; }
.prefs-pane-button button:dir(rtl) {
left: 5px;
right: auto; }

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

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><g fill="none" stroke="#4d4d4d" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M8 1v3M8 12v3M4.5 11.5l-1.45 1.45M12.95 3.05L11 5M1 8h3M12 8h3"/><circle cx="8" cy="8" r="4"/><path d="M3.05 3.05L5 5M11 11l1.95 1.95"/></g></svg>

После

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

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -23,6 +23,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "NewTabInit",
XPCOMUtils.defineLazyModuleGetter(this, "PlacesFeed",
"resource://activity-stream/lib/PlacesFeed.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrefsFeed",
"resource://activity-stream/lib/PrefsFeed.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryFeed",
"resource://activity-stream/lib/TelemetryFeed.jsm");
@ -53,6 +56,12 @@ const PREFS_CONFIG = [
value: true,
init: () => new PlacesFeed()
},
{
name: "feeds.prefs",
title: "Preferences",
value: true,
init: () => new PrefsFeed(PREFS_CONFIG.map(pref => pref.name))
},
{
name: "feeds.telemetry",
title: "Relays telemetry-related actions to TelemetrySender",
@ -65,8 +74,16 @@ const PREFS_CONFIG = [
value: true,
init: () => new TopSitesFeed()
},
// End feeds
{
name: "showSearch",
title: "Show the Search bar on the New Tab page",
value: true
},
{
name: "showTopSites",
title: "Show the Top Sites section on the New Tab page",
value: true
},
{
name: "telemetry",
title: "Enable system error and usage data collection",
@ -133,4 +150,5 @@ this.ActivityStream = class ActivityStream {
}
};
this.PREFS_CONFIG = PREFS_CONFIG;
this.EXPORTED_SYMBOLS = ["ActivityStream"];

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

@ -0,0 +1,59 @@
/* 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/. */
"use strict";
const {utils: Cu} = Components;
const {actionTypes: at, actionCreators: ac} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Prefs",
"resource://activity-stream/lib/ActivityStreamPrefs.jsm");
this.PrefsFeed = class PrefsFeed {
constructor(prefNames) {
this._prefNames = prefNames;
this._prefs = new Prefs();
this._observers = new Map();
}
onPrefChanged(name, value) {
this.store.dispatch(ac.BroadcastToContent({type: at.PREF_CHANGED, data: {name, value}}));
}
init() {
const values = {};
// Set up listeners for each activity stream pref
for (const name of this._prefNames) {
const handler = value => {
this.onPrefChanged(name, value);
};
this._observers.set(name, handler, this);
this._prefs.observe(name, handler);
values[name] = this._prefs.get(name);
}
// Set the initial state of all prefs in redux
this.store.dispatch(ac.BroadcastToContent({type: at.PREFS_INITIAL_VALUES, data: values}));
}
removeListeners() {
for (const name of this._prefNames) {
this._prefs.ignore(name, this._observers.get(name));
}
this._observers.clear();
}
onAction(action) {
switch (action.type) {
case at.INIT:
this.init();
break;
case at.UNINIT:
this.removeListeners();
break;
case at.SET_PREF:
this._prefs.set(action.data.name, action.data.value);
break;
}
}
};
this.EXPORTED_SYMBOLS = ["PrefsFeed"];

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

@ -33,6 +33,8 @@ const UserEventAction = Joi.object().keys({
"DELETE",
"OPEN_NEW_WINDOW",
"OPEN_PRIVATE_WINDOW",
"OPEN_NEWTAB_PREFS",
"CLOSE_NEWTAB_PREFS",
"BOOKMARK_DELETE",
"BOOKMARK_ADD"
]).required(),

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

@ -1,5 +1,5 @@
const {reducers, INITIAL_STATE} = require("common/Reducers.jsm");
const {TopSites, App} = reducers;
const {TopSites, App, Prefs} = reducers;
const {actionTypes: at} = require("common/Actions.jsm");
describe("Reducers", () => {
@ -108,4 +108,43 @@ describe("Reducers", () => {
});
});
});
describe("Prefs", () => {
function prevState(custom = {}) {
return Object.assign({}, INITIAL_STATE.Prefs, custom);
}
it("should have the correct initial state", () => {
const state = Prefs(undefined, {});
assert.deepEqual(state, INITIAL_STATE.Prefs);
});
describe("PREFS_INITIAL_VALUES", () => {
it("should return a new object", () => {
const state = Prefs(undefined, {type: at.PREFS_INITIAL_VALUES, data: {}});
assert.notEqual(INITIAL_STATE.Prefs, state, "should not modify INITIAL_STATE");
});
it("should set initalized to true", () => {
const state = Prefs(undefined, {type: at.PREFS_INITIAL_VALUES, data: {}});
assert.isTrue(state.initialized);
});
it("should set .values", () => {
const newValues = {foo: 1, bar: 2};
const state = Prefs(undefined, {type: at.PREFS_INITIAL_VALUES, data: newValues});
assert.equal(state.values, newValues);
});
});
describe("PREF_CHANGED", () => {
it("should return a new Prefs object", () => {
const state = Prefs(undefined, {type: at.PREF_CHANGED, data: {name: "foo", value: 2}});
assert.notEqual(INITIAL_STATE.Prefs, state, "should not modify INITIAL_STATE");
});
it("should set the changed pref", () => {
const state = Prefs(prevState({foo: 1}), {type: at.PREF_CHANGED, data: {name: "foo", value: 2}});
assert.equal(state.values.foo, 2);
});
it("should return a new .pref object instead of mutating", () => {
const oldState = prevState({foo: 1});
const state = Prefs(oldState, {type: at.PREF_CHANGED, data: {name: "foo", value: 2}});
assert.notEqual(oldState.values, state.values);
});
});
});
});

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

@ -15,7 +15,8 @@ describe("ActivityStream", () => {
"lib/NewTabInit.jsm": {NewTabInit: Fake},
"lib/PlacesFeed.jsm": {PlacesFeed: Fake},
"lib/TelemetryFeed.jsm": {TelemetryFeed: Fake},
"lib/TopSitesFeed.jsm": {TopSitesFeed: Fake}
"lib/TopSitesFeed.jsm": {TopSitesFeed: Fake},
"lib/PrefsFeed.jsm": {PrefsFeed: Fake}
}));
as = new ActivityStream();
sandbox.stub(as.store, "init");
@ -101,5 +102,9 @@ describe("ActivityStream", () => {
const feed = as.feeds["feeds.telemetry"]();
assert.instanceOf(feed, Fake);
});
it("should create a Prefs feed", () => {
const feed = as.feeds["feeds.prefs"]();
assert.instanceOf(feed, Fake);
});
});
});

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

@ -0,0 +1,54 @@
const {PrefsFeed} = require("lib/PrefsFeed.jsm");
const {actionTypes: at, actionCreators: ac} = require("common/Actions.jsm");
const FAKE_PREFS = [{name: "foo", value: 1}, {name: "bar", value: 2}];
describe("PrefsFeed", () => {
let feed;
beforeEach(() => {
feed = new PrefsFeed(FAKE_PREFS.map(p => p.name));
feed.store = {dispatch: sinon.spy()};
feed._prefs = {
get: sinon.spy(item => FAKE_PREFS.filter(p => p.name === item)[0].value),
set: sinon.spy(),
observe: sinon.spy(),
ignore: sinon.spy()
};
});
it("should set a pref when a SET_PREF action is received", () => {
feed.onAction(ac.SetPref("foo", 2));
assert.calledWith(feed._prefs.set, "foo", 2);
});
it("should dispatch PREFS_INITIAL_VALUES on init", () => {
feed.onAction({type: at.INIT});
assert.calledOnce(feed.store.dispatch);
assert.equal(feed.store.dispatch.firstCall.args[0].type, at.PREFS_INITIAL_VALUES);
assert.deepEqual(feed.store.dispatch.firstCall.args[0].data, {foo: 1, bar: 2});
});
it("should add one observer per pref on init", () => {
feed.onAction({type: at.INIT});
FAKE_PREFS.forEach(pref => {
assert.calledWith(feed._prefs.observe, pref.name);
assert.isTrue(feed._observers.has(pref.name));
});
});
it("should call onPrefChanged when an observer is called", () => {
sinon.stub(feed, "onPrefChanged");
feed.onAction({type: at.INIT});
const handlerForFoo = feed._observers.get("foo");
handlerForFoo(true);
assert.calledWith(feed.onPrefChanged, "foo", true);
});
it("should remove all observers on uninit", () => {
feed.onAction({type: at.UNINIT});
FAKE_PREFS.forEach(pref => {
assert.calledWith(feed._prefs.ignore, pref.name);
});
});
it("should send a PREF_CHANGED action when onPrefChanged is called", () => {
feed.onPrefChanged("foo", 2);
assert.calledWith(feed.store.dispatch, ac.BroadcastToContent({type: at.PREF_CHANGED, data: {name: "foo", value: 2}}));
});
});

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

@ -124,11 +124,9 @@ FormAutofillHandler.prototype = {
if (option.selected) {
break;
}
// TODO: Using dispatchEvent does not 100% simulate select change.
// Should investigate further in Bug 1365895.
option.selected = true;
element.dispatchEvent(new Event("input", {"bubbles": true}));
element.dispatchEvent(new Event("change", {"bubbles": true}));
element.dispatchEvent(new element.ownerGlobal.UIEvent("input", {bubbles: true}));
element.dispatchEvent(new element.ownerGlobal.Event("change", {bubbles: true}));
this.changeFieldState(fieldDetail, "AUTO_FILLED");
break;
}

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

@ -47,13 +47,22 @@ function popupShownListener() {
}
}
function checkInputFilled(element, expectedvalue) {
return new Promise(resolve => {
element.addEventListener("change", function onChange() {
is(element.value, expectedvalue, "Checking " + element.name + " field");
resolve();
}, {once: true});
});
function checkElementFilled(element, expectedvalue) {
return [
new Promise(resolve => {
element.addEventListener("input", function onInput() {
ok(true, "Checking " + element.name + " field fires input event");
resolve();
}, {once: true});
}),
new Promise(resolve => {
element.addEventListener("change", function onChange() {
ok(true, "Checking " + element.name + " field fires change event");
is(element.value, expectedvalue, "Checking " + element.name + " field");
resolve();
}, {once: true});
}),
];
}
function checkAutoCompleteInputFilled(element, expectedvalue) {
@ -72,7 +81,7 @@ function checkFormFilled(address) {
if (document.activeElement == element) {
promises.push(checkAutoCompleteInputFilled(element, address[prop]));
} else {
promises.push(checkInputFilled(element, address[prop]));
promises.push(...checkElementFilled(element, address[prop]));
}
}
doKey("return");

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

@ -273,7 +273,7 @@ function do_test(testcases, testFn) {
// be filled.
return;
}
promises.push(testFn(testcase, element));
promises.push(...testFn(testcase, element));
});
handler.autofillFormFields(testcase.profileData);
@ -286,33 +286,44 @@ function do_test(testcases, testFn) {
}
do_test(TESTCASES, (testcase, element) => {
return new Promise(resolve => {
element.addEventListener("change", () => {
let id = element.id;
Assert.equal(element.value, testcase.expectedResult[id],
"Check the " + id + " field was filled with correct data");
resolve();
}, {once: true});
});
let id = element.id;
return [
new Promise(resolve => {
element.addEventListener("input", () => {
Assert.ok(true, "Checking " + id + " field fires input event");
resolve();
}, {once: true});
}),
new Promise(resolve => {
element.addEventListener("change", () => {
Assert.ok(true, "Checking " + id + " field fires change event");
Assert.equal(element.value, testcase.expectedResult[id],
"Check the " + id + " field was filled with correct data");
resolve();
}, {once: true});
}),
];
});
do_test(TESTCASES_INPUT_UNCHANGED, (testcase, element) => {
return new Promise((resolve, reject) => {
// Make sure no change or input event is fired when no change occurs.
let cleaner;
let timer = setTimeout(() => {
let id = element.id;
element.removeEventListener("change", cleaner);
element.removeEventListener("input", cleaner);
Assert.equal(element.value, testcase.expectedResult[id],
"Check no value is changed on the " + id + " field");
resolve();
}, 1000);
cleaner = event => {
clearTimeout(timer);
reject(`${event.type} event should not fire`);
};
element.addEventListener("change", cleaner);
element.addEventListener("input", cleaner);
});
return [
new Promise((resolve, reject) => {
// Make sure no change or input event is fired when no change occurs.
let cleaner;
let timer = setTimeout(() => {
let id = element.id;
element.removeEventListener("change", cleaner);
element.removeEventListener("input", cleaner);
Assert.equal(element.value, testcase.expectedResult[id],
"Check no value is changed on the " + id + " field");
resolve();
}, 1000);
cleaner = event => {
clearTimeout(timer);
reject(`${event.type} event should not fire`);
};
element.addEventListener("change", cleaner);
element.addEventListener("input", cleaner);
}),
];
});

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

@ -85,8 +85,7 @@
<!ENTITY col.dateadded.label "Added">
<!ENTITY col.lastmodified.label "Last Modified">
<!ENTITY search.label "Search:">
<!ENTITY search.accesskey "S">
<!ENTITY search.placeholder "Search">
<!ENTITY cmd.find.key "f">
@ -104,8 +103,6 @@
<!ENTITY detailsPane.less.accesskey "e">
<!ENTITY detailsPane.selectAnItemText.description "Select an item to view and edit its properties">
<!ENTITY find.label "Search:">
<!ENTITY find.accesskey "S">
<!ENTITY view.label "View">
<!ENTITY view.accesskey "w">
<!ENTITY byDate.label "By Date">

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

@ -1063,10 +1063,15 @@ function updateIndicators(data, target) {
}
if (webrtcUI.showGlobalIndicator) {
if (!gIndicatorWindow)
if (!gIndicatorWindow) {
gIndicatorWindow = getGlobalIndicator();
else
gIndicatorWindow.updateIndicatorState();
} else {
try {
gIndicatorWindow.updateIndicatorState();
} catch (err) {
Cu.reportError(`error in gIndicatorWindow.updateIndicatorState(): ${err.message}`);
}
}
} else if (gIndicatorWindow) {
gIndicatorWindow.close();
gIndicatorWindow = null;

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

@ -954,6 +954,14 @@ html|span.ac-emphasize-text-url {
background-color: Window;
}
#sidebar-header {
border-bottom: 1px solid ThreeDShadow;
}
.sidebar-splitter {
border-color: ThreeDShadow;
}
.browserContainer > findbar {
background-color: -moz-dialog;
color: -moz-DialogText;

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

@ -1367,8 +1367,13 @@ html|span.ac-emphasize-text-url {
}
#sidebar-header {
/* match the font size used in side bar content */
font-size: 12px;
/* system font size is a bit smaller in mac, so need more ems. */
font-size: 1.4545em;
border-bottom: 1px solid hsla(240, 5%, 5%, .1);
}
.sidebar-splitter {
border-color: hsla(240, 5%, 5%, .1);
}
/* ----- CONTENT ----- */

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

@ -6,7 +6,7 @@
/* Sidebars */
#bookmarksPanel,
#bookmarksPanel,
#history-panel,
#sidebar-search-container,
#tabs-panel {
@ -27,10 +27,6 @@
-moz-appearance: -moz-mac-source-list;
}
.sidebar-placesTreechildren {
border-top: 1px solid #bebebe;
}
.sidebar-placesTreechildren::-moz-tree-separator {
border-top: 1px solid #505d6d;
margin: 0 10px;
@ -163,7 +159,7 @@
}
#sidebar-search-container {
margin: 0 4px 6px;
margin: 0 4px;
}
/* Trees */

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

@ -4,22 +4,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
%endif
#sidebar-box {
--header-background-color: #F2F2F2;
--header-background-color-hover: rgba(204, 204, 204, 0.6);
}
.sidebar-header,
#sidebar-header {
padding: 4px;
background-color: var(--header-background-color);
font-size: 1.333em;
font-weight: lighter;
padding: 8px;
color: -moz-dialogText;
text-shadow: none;
}
.sidebar-splitter {
-moz-appearance: none;
border: 0 solid #ccc;
border: 0 solid;
border-inline-end-width: 1px;
min-width: 1px;
width: 3px;
@ -73,18 +69,36 @@
list-style-image: url(chrome://browser/skin/sidebar/close.svg);
margin: 0;
padding: 4px;
border-radius: 4px;
}
#sidebar-switcher-target {
-moz-appearance: none;
padding: 4px;
color: inherit;
margin-inline-end: 4px;
border-radius: 4px;
border: 1px solid transparent;
padding: 2px 4px;
}
#sidebar-box #sidebar-switcher-target:hover,
#sidebar-switcher-target.active,
#sidebar-switcher-target:hover,
#sidebar-close:hover {
background: var(--header-background-color-hover);
background: hsla(240, 5%, 5%, 0.05);
}
#sidebar-switcher-target:hover {
border-color: rgba(0, 0, 0, 0.2);
}
#sidebar-close:hover:active,
#sidebar-switcher-target:hover:active,
#sidebar-switcher-target.active {
background: hsla(240, 5%, 5%, 0.1);
}
#sidebar-switcher-target:hover:active,
#sidebar-switcher-target.active {
border-color: rgba(0, 0, 0, 0.25);
}
#sidebarMenu-popup .subviewbutton {

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

@ -1377,6 +1377,14 @@ treechildren.searchbar-treebody::-moz-tree-row(selected) {
background-color: Window;
}
#sidebar-header {
border-bottom: 1px solid ThreeDLightShadow;
}
.sidebar-splitter {
border-color: ThreeDLightShadow;
}
.browserContainer > findbar {
background-color: -moz-dialog;
color: -moz-DialogText;

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

@ -1,14 +1,16 @@
ac_add_options --target=x86_64-pc-mingw32
ac_add_options --host=x86_64-pc-mingw32
CLANG_LIB_DIR="$(cd $topsrcdir/clang/lib/clang/* && cd lib/windows && pwd)"
if [ -d "$topsrcdir/clang" ]; then
CLANG_LIB_DIR="$(cd $topsrcdir/clang/lib/clang/* && cd lib/windows && pwd)"
export LIB=$LIB:$CLANG_LIB_DIR
mk_export_correct_style LIB
export LDFLAGS="clang_rt.asan_dynamic-x86_64.lib clang_rt.asan_dynamic_runtime_thunk-x86_64.lib"
export LIB=$LIB:$CLANG_LIB_DIR
mk_export_correct_style LIB
export LDFLAGS="clang_rt.asan_dynamic-x86_64.lib clang_rt.asan_dynamic_runtime_thunk-x86_64.lib"
export LLVM_SYMBOLIZER="$topsrcdir/clang/bin/llvm-symbolizer.exe"
export MOZ_CLANG_RT_ASAN_LIB_PATH="${CLANG_LIB_DIR}/clang_rt.asan_dynamic-x86_64.dll"
export LLVM_SYMBOLIZER="$topsrcdir/clang/bin/llvm-symbolizer.exe"
export MOZ_CLANG_RT_ASAN_LIB_PATH="${CLANG_LIB_DIR}/clang_rt.asan_dynamic-x86_64.dll"
fi
# Enable ASan specific code and build workarounds
ac_add_options --enable-address-sanitizer

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

@ -214,7 +214,17 @@ let addonSdkMethods = [
"showToolbox",
];
for (let method of addonSdkMethods) {
/**
* Compatibility layer for webextensions.
*
* Those methods are called only after a DevTools webextension was loaded in DevTools,
* therefore DevTools should always be available when they are called.
*/
let webExtensionsMethods = [
"getTheme",
];
for (let method of [...addonSdkMethods, ...webExtensionsMethods]) {
this.DevToolsShim[method] = function () {
if (!this.isInstalled()) {
throw new Error(`Method ${method} unavailable if DevTools are not installed`);

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

@ -531,20 +531,60 @@ public:
bool LessThan(SimpleTextTrackEvent* aOne, SimpleTextTrackEvent* aTwo) const
{
// TimeMarchesOn step 13.1.
if (aOne->mTime < aTwo->mTime) {
return true;
} else if (aOne->mTime > aTwo->mTime) {
return false;
}
int32_t positionOne = TrackChildPosition(aOne);
int32_t positionTwo = TrackChildPosition(aTwo);
if (positionOne < positionTwo) {
return true;
} else if (positionOne > positionTwo) {
return false;
// TimeMarchesOn step 13.2 text track cue order.
// TextTrack position in TextTrackList
TextTrack* t1 = aOne->mTrack;
TextTrack* t2 = aTwo->mTrack;
MOZ_ASSERT(t1, "CompareSimpleTextTrackEvents t1 is null");
MOZ_ASSERT(t2, "CompareSimpleTextTrackEvents t2 is null");
if (t1 != t2) {
TextTrackList* tList= t1->GetTextTrackList();
MOZ_ASSERT(tList, "CompareSimpleTextTrackEvents tList is null");
nsTArray<RefPtr<TextTrack>>& textTracks = tList->GetTextTrackArray();
auto index1 = textTracks.IndexOf(t1);
auto index2 = textTracks.IndexOf(t2);
if (index1 < index2) {
return true;
} else if (index1 > index2) {
return false;
}
}
MOZ_ASSERT(t1 == t2, "CompareSimpleTextTrackEvents t1 != t2");
// c1 and c2 are both belongs to t1.
TextTrackCue* c1 = aOne->mCue;
TextTrackCue* c2 = aTwo->mCue;
if (c1 != c2) {
if (c1->StartTime() < c2->StartTime()) {
return true;
} else if (c1->StartTime() > c2->StartTime()) {
return false;
}
if (c1->EndTime() < c2->EndTime()) {
return true;
} else if (c1->EndTime() > c2->EndTime()) {
return false;
}
TextTrackCueList* cueList = t1->GetCues();
nsTArray<RefPtr<TextTrackCue>>& cues = cueList->GetCuesArray();
auto index1 = cues.IndexOf(c1);
auto index2 = cues.IndexOf(c2);
if (index1 < index2) {
return true;
} else if (index1 > index2) {
return false;
}
}
// TimeMarchesOn step 13.3.
if (aOne->mName.EqualsLiteral("enter") ||
aTwo->mName.EqualsLiteral("exit")) {
return true;

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

@ -8,7 +8,6 @@
#include "MediaStreamGraph.h"
#include "mozilla/dom/MediaStreamTrack.h"
#include "GetUserMediaRequest.h"
#include "MediaStreamListener.h"
#include "nsArray.h"
#include "nsContentUtils.h"
@ -1291,6 +1290,10 @@ public:
callback.forget(),
self->mWindowID,
self->mOnFailure.forget())));
NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
RefPtr<MediaManager> manager = MediaManager::GetInstance();
manager->SendPendingGUMRequest();
}));
return NS_OK;
}));
@ -1539,6 +1542,10 @@ public:
Fail(NS_LITERAL_STRING("NotReadableError"),
NS_ConvertUTF8toUTF16(errorMsg));
}
NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
RefPtr<MediaManager> manager = MediaManager::GetInstance();
manager->SendPendingGUMRequest();
}));
return NS_OK;
}
PeerIdentity* peerIdentity = nullptr;
@ -2470,7 +2477,13 @@ MediaManager::GetUserMedia(nsPIDOMWindowInner* aWindow,
} else {
RefPtr<GetUserMediaRequest> req =
new GetUserMediaRequest(window, callID, c, isHTTPS);
obs->NotifyObservers(req, "getUserMedia:request", nullptr);
if (!Preferences::GetBool("media.navigator.permission.force") && array->Length() > 1) {
// there is at least 1 pending gUM request
// For the scarySources test case, always send the request
self->mPendingGUMRequest.AppendElement(req.forget());
} else {
obs->NotifyObservers(req, "getUserMedia:request", nullptr);
}
}
#ifdef MOZ_WEBRTC
@ -2950,6 +2963,7 @@ MediaManager::Shutdown()
GetActiveWindows()->Clear();
mActiveCallbacks.Clear();
mCallIds.Clear();
mPendingGUMRequest.Clear();
#ifdef MOZ_WEBRTC
StopWebRtcLog();
#endif
@ -3024,6 +3038,16 @@ MediaManager::Shutdown()
mMediaThread->message_loop()->PostTask(shutdown.forget());
}
void
MediaManager::SendPendingGUMRequest()
{
if (mPendingGUMRequest.Length() > 0) {
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
obs->NotifyObservers(mPendingGUMRequest[0], "getUserMedia:request", nullptr);
mPendingGUMRequest.RemoveElementAt(0);
}
}
nsresult
MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
@ -3132,6 +3156,7 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
return NS_OK;
}
array->RemoveElement(key);
SendPendingGUMRequest();
}
return NS_OK;

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

@ -7,6 +7,7 @@
#include "MediaEngine.h"
#include "mozilla/media/DeviceChangeCallback.h"
#include "mozilla/dom/GetUserMediaRequest.h"
#include "mozilla/Services.h"
#include "mozilla/Unused.h"
#include "nsAutoPtr.h"
@ -228,6 +229,7 @@ public:
}
void AddWindowID(uint64_t aWindowId, GetUserMediaWindowListener *aListener);
void RemoveWindowID(uint64_t aWindowId);
void SendPendingGUMRequest();
bool IsWindowStillActive(uint64_t aWindowId) {
return !!GetWindowListener(aWindowId);
}
@ -314,6 +316,7 @@ private:
WindowTable mActiveWindows;
nsRefPtrHashtable<nsStringHashKey, GetUserMediaTask> mActiveCallbacks;
nsClassHashtable<nsUint64HashKey, nsTArray<nsString>> mCallIds;
nsTArray<RefPtr<dom::GetUserMediaRequest>> mPendingGUMRequest;
// Always exists
nsAutoPtr<base::Thread> mMediaThread;

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

@ -169,5 +169,11 @@ TextTrackCueList::IsCueExist(TextTrackCue *aCue)
return false;
}
nsTArray<RefPtr<TextTrackCue>>&
TextTrackCueList::GetCuesArray()
{
return mList;
}
} // namespace dom
} // namespace mozilla

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

@ -67,6 +67,7 @@ public:
GetCueListByTimeInterval(media::Interval<double>& aInterval);
void NotifyCueUpdated(TextTrackCue *aCue);
bool IsCueExist(TextTrackCue *aCue);
nsTArray<RefPtr<TextTrackCue>>& GetCuesArray();
private:
~TextTrackCueList();

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

@ -257,5 +257,11 @@ bool TextTrackList::AreTextTracksLoaded()
return true;
}
nsTArray<RefPtr<TextTrack>>&
TextTrackList::GetTextTrackArray()
{
return mTextTracks;
}
} // namespace dom
} // namespace mozilla

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

@ -64,6 +64,7 @@ public:
void SetCuesInactive();
bool AreTextTracksLoaded();
nsTArray<RefPtr<TextTrack>>& GetTextTrackArray();
IMPL_EVENT_HANDLER(change)
IMPL_EVENT_HANDLER(addtrack)

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

@ -59,23 +59,23 @@ mozilla::StereoMode getStereoMode(int aMode)
}
}
// HlsDemuxerCallbacksSupport is a native implemented callback class for
// HlsDemuxerCallbacks in GeckoHlsDemuxerWrapper.java.
// HLSDemuxerCallbacksSupport is a native implemented callback class for
// Callbacks in GeckoHLSDemuxerWrapper.java.
// The callback functions will be invoked by JAVA-side thread.
// Should dispatch the task to the demuxer's task queue.
// We ensure the callback will never be invoked after
// HlsDemuxerCallbacksSupport::DisposeNative has been called in ~HLSDemuxer.
class HLSDemuxer::HlsDemuxerCallbacksSupport
: public GeckoHlsDemuxerWrapper::HlsDemuxerCallbacks::Natives<HlsDemuxerCallbacksSupport>
// HLSDemuxerCallbacksSupport::DisposeNative has been called in ~HLSDemuxer.
class HLSDemuxer::HLSDemuxerCallbacksSupport
: public GeckoHLSDemuxerWrapper::Callbacks::Natives<HLSDemuxerCallbacksSupport>
{
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HlsDemuxerCallbacksSupport)
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HLSDemuxerCallbacksSupport)
public:
typedef GeckoHlsDemuxerWrapper::HlsDemuxerCallbacks::Natives<HlsDemuxerCallbacksSupport> NativeCallbacks;
typedef GeckoHLSDemuxerWrapper::Callbacks::Natives<HLSDemuxerCallbacksSupport> NativeCallbacks;
using NativeCallbacks::DisposeNative;
using NativeCallbacks::AttachNative;
HlsDemuxerCallbacksSupport(HLSDemuxer* aDemuxer)
: mMutex("HlsDemuxerCallbacksSupport")
HLSDemuxerCallbacksSupport(HLSDemuxer* aDemuxer)
: mMutex("HLSDemuxerCallbacksSupport")
, mDemuxer(aDemuxer)
{
MOZ_ASSERT(mDemuxer);
@ -83,11 +83,11 @@ public:
void OnInitialized(bool aHasAudio, bool aHasVideo)
{
HLS_DEBUG("HlsDemuxerCallbacksSupport",
HLS_DEBUG("HLSDemuxerCallbacksSupport",
"OnInitialized");
MutexAutoLock lock(mMutex);
if (!mDemuxer) { return; }
RefPtr<HlsDemuxerCallbacksSupport> self = this;
RefPtr<HLSDemuxerCallbacksSupport> self = this;
mDemuxer->GetTaskQueue()->Dispatch(NS_NewRunnableFunction(
[=] () {
MutexAutoLock lock(self->mMutex);
@ -101,7 +101,7 @@ public:
// in bug 1368904.
void OnError(int aErrorCode)
{
HLS_DEBUG("HlsDemuxerCallbacksSupport",
HLS_DEBUG("HLSDemuxerCallbacksSupport",
"Got error(%d) from java side",
aErrorCode);
}
@ -113,7 +113,7 @@ public:
Mutex mMutex;
private:
~HlsDemuxerCallbacksSupport() { }
~HLSDemuxerCallbacksSupport() { }
HLSDemuxer* mDemuxer;
};
@ -126,17 +126,17 @@ HLSDemuxer::HLSDemuxer(MediaResource* aResource)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aResource);
HlsDemuxerCallbacksSupport::Init();
mJavaCallbacks = GeckoHlsDemuxerWrapper::HlsDemuxerCallbacks::New();
HLSDemuxerCallbacksSupport::Init();
mJavaCallbacks = GeckoHLSDemuxerWrapper::Callbacks::New();
MOZ_ASSERT(mJavaCallbacks);
mCallbackSupport = new HlsDemuxerCallbacksSupport(this);
HlsDemuxerCallbacksSupport::AttachNative(mJavaCallbacks,
mCallbackSupport = new HLSDemuxerCallbacksSupport(this);
HLSDemuxerCallbacksSupport::AttachNative(mJavaCallbacks,
mCallbackSupport);
auto resourceWrapper = static_cast<HLSResource*>(aResource)->GetResourceWrapper();
mHlsDemuxerWrapper = GeckoHlsDemuxerWrapper::Create(resourceWrapper->GetPlayer(), mJavaCallbacks);
MOZ_ASSERT(mHlsDemuxerWrapper);
mHLSDemuxerWrapper = GeckoHLSDemuxerWrapper::Create(resourceWrapper->GetPlayer(), mJavaCallbacks);
MOZ_ASSERT(mHLSDemuxerWrapper);
}
void
@ -190,9 +190,9 @@ HLSDemuxer::GetNumberTracks(TrackType aType) const
{
switch (aType) {
case TrackType::kAudioTrack:
return mHlsDemuxerWrapper->GetNumberOfTracks(TrackType::kAudioTrack);
return mHLSDemuxerWrapper->GetNumberOfTracks(TrackType::kAudioTrack);
case TrackType::kVideoTrack:
return mHlsDemuxerWrapper->GetNumberOfTracks(TrackType::kVideoTrack);
return mHLSDemuxerWrapper->GetNumberOfTracks(TrackType::kVideoTrack);
default:
return 0;
}
@ -209,7 +209,7 @@ HLSDemuxer::GetTrackDemuxer(TrackType aType, uint32_t aTrackNumber)
bool
HLSDemuxer::IsSeekable() const
{
return !mHlsDemuxerWrapper->IsLiveStream();
return !mHLSDemuxerWrapper->IsLiveStream();
}
UniquePtr<EncryptionInfo>
@ -239,18 +239,18 @@ HLSDemuxer::GetTrackInfo(TrackType aTrack)
TimeUnit
HLSDemuxer::GetNextKeyFrameTime()
{
MOZ_ASSERT(mHlsDemuxerWrapper);
return TimeUnit::FromMicroseconds(mHlsDemuxerWrapper->GetNextKeyFrameTime());
MOZ_ASSERT(mHLSDemuxerWrapper);
return TimeUnit::FromMicroseconds(mHLSDemuxerWrapper->GetNextKeyFrameTime());
}
void
HLSDemuxer::UpdateAudioInfo(int index)
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(mHlsDemuxerWrapper);
MOZ_ASSERT(mHLSDemuxerWrapper);
HLS_DEBUG("HLSDemuxer", "UpdateAudioInfo (%d)", index);
MutexAutoLock lock(mMutex);
jni::Object::LocalRef infoObj = mHlsDemuxerWrapper->GetAudioInfo(index);
jni::Object::LocalRef infoObj = mHLSDemuxerWrapper->GetAudioInfo(index);
if (infoObj) {
java::GeckoAudioInfo::LocalRef audioInfo(Move(infoObj));
mInfo.mAudio.mRate = audioInfo->Rate();
@ -270,9 +270,9 @@ void
HLSDemuxer::UpdateVideoInfo(int index)
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(mHlsDemuxerWrapper);
MOZ_ASSERT(mHLSDemuxerWrapper);
MutexAutoLock lock(mMutex);
jni::Object::LocalRef infoObj = mHlsDemuxerWrapper->GetVideoInfo(index);
jni::Object::LocalRef infoObj = mHLSDemuxerWrapper->GetVideoInfo(index);
if (infoObj) {
java::GeckoVideoInfo::LocalRef videoInfo(Move(infoObj));
mInfo.mVideo.mStereoMode = getStereoMode(videoInfo->StereoMode());
@ -300,12 +300,12 @@ HLSDemuxer::~HLSDemuxer()
HLS_DEBUG("HLSDemuxer", "~HLSDemuxer()");
mCallbackSupport->Detach();
if (mJavaCallbacks) {
HlsDemuxerCallbacksSupport::DisposeNative(mJavaCallbacks);
HLSDemuxerCallbacksSupport::DisposeNative(mJavaCallbacks);
mJavaCallbacks = nullptr;
}
if (mHlsDemuxerWrapper) {
mHlsDemuxerWrapper->Destroy();
mHlsDemuxerWrapper = nullptr;
if (mHLSDemuxerWrapper) {
mHLSDemuxerWrapper->Destroy();
mHLSDemuxerWrapper = nullptr;
}
mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
}
@ -340,7 +340,7 @@ HLSTrackDemuxer::DoSeek(const TimeUnit& aTime)
MOZ_ASSERT(mParent->OnTaskQueue());
mQueuedSample = nullptr;
int64_t seekTimeUs = aTime.ToMicroseconds();
bool result = mParent->mHlsDemuxerWrapper->Seek(seekTimeUs);
bool result = mParent->mHLSDemuxerWrapper->Seek(seekTimeUs);
if (!result) {
return SeekPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA,
__func__);
@ -384,8 +384,8 @@ HLSTrackDemuxer::DoGetSamples(int32_t aNumSamples)
}
mozilla::jni::ObjectArray::LocalRef demuxedSamples =
(mType == TrackInfo::kAudioTrack)
? mParent->mHlsDemuxerWrapper->GetSamples(TrackInfo::kAudioTrack, aNumSamples)
: mParent->mHlsDemuxerWrapper->GetSamples(TrackInfo::kVideoTrack, aNumSamples);
? mParent->mHLSDemuxerWrapper->GetSamples(TrackInfo::kAudioTrack, aNumSamples)
: mParent->mHLSDemuxerWrapper->GetSamples(TrackInfo::kVideoTrack, aNumSamples);
nsTArray<jni::Object::LocalRef> sampleObjectArray(demuxedSamples->GetElements());
if (sampleObjectArray.IsEmpty()) {
@ -393,7 +393,7 @@ HLSTrackDemuxer::DoGetSamples(int32_t aNumSamples)
}
for (auto&& demuxedSample : sampleObjectArray) {
java::GeckoHlsSample::LocalRef sample(Move(demuxedSample));
java::GeckoHLSSample::LocalRef sample(Move(demuxedSample));
if (sample->IsEOS()) {
HLS_DEBUG("HLSTrackDemuxer", "Met BUFFER_FLAG_END_OF_STREAM.");
if (samples->mSamples.IsEmpty()) {
@ -496,7 +496,7 @@ HLSTrackDemuxer::ExtractCryptoSample(size_t aSampleSize,
}
RefPtr<MediaRawData>
HLSTrackDemuxer::ConvertToMediaRawData(java::GeckoHlsSample::LocalRef aSample)
HLSTrackDemuxer::ConvertToMediaRawData(java::GeckoHLSSample::LocalRef aSample)
{
java::sdk::BufferInfo::LocalRef info = aSample->Info();
// Currently extract PTS, Size and Data without Crypto information.
@ -596,14 +596,14 @@ HLSTrackDemuxer::DoSkipToNextRandomAccessPoint(
MediaResult result = NS_ERROR_DOM_MEDIA_END_OF_STREAM;
do {
mozilla::jni::ObjectArray::LocalRef demuxedSamples =
mParent->mHlsDemuxerWrapper->GetSamples(mType, 1);
mParent->mHLSDemuxerWrapper->GetSamples(mType, 1);
nsTArray<jni::Object::LocalRef> sampleObjectArray(demuxedSamples->GetElements());
if (sampleObjectArray.IsEmpty()) {
result = NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA;
break;
}
parsed++;
java::GeckoHlsSample::LocalRef sample(Move(sampleObjectArray[0]));
java::GeckoHLSSample::LocalRef sample(Move(sampleObjectArray[0]));
if (sample->IsEOS()) {
result = NS_ERROR_DOM_MEDIA_END_OF_STREAM;
break;
@ -632,7 +632,7 @@ TimeIntervals
HLSTrackDemuxer::GetBuffered()
{
MOZ_ASSERT(mParent, "Called after BreackCycle()");
int64_t bufferedTime = mParent->mHlsDemuxerWrapper->GetBuffered(); //us
int64_t bufferedTime = mParent->mHLSDemuxerWrapper->GetBuffered(); //us
return TimeIntervals(TimeInterval(TimeUnit(),
TimeUnit::FromMicroseconds(bufferedTime)));
}

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

@ -27,7 +27,7 @@ class HLSTrackDemuxer;
class HLSDemuxer final : public MediaDataDemuxer
{
class HlsDemuxerCallbacksSupport;
class HLSDemuxerCallbacksSupport;
public:
explicit HLSDemuxer(MediaResource* aResource);
@ -65,14 +65,14 @@ private:
nsTArray<RefPtr<HLSTrackDemuxer>> mDemuxers;
MozPromiseHolder<InitPromise> mInitPromise;
RefPtr<HlsDemuxerCallbacksSupport> mCallbackSupport;
RefPtr<HLSDemuxerCallbacksSupport> mCallbackSupport;
// Mutex to protect members below across multiple threads.
mutable Mutex mMutex;
MediaInfo mInfo;
java::GeckoHlsDemuxerWrapper::HlsDemuxerCallbacks::GlobalRef mJavaCallbacks;
java::GeckoHlsDemuxerWrapper::GlobalRef mHlsDemuxerWrapper;
java::GeckoHLSDemuxerWrapper::Callbacks::GlobalRef mJavaCallbacks;
java::GeckoHLSDemuxerWrapper::GlobalRef mHLSDemuxerWrapper;
};
class HLSTrackDemuxer : public MediaTrackDemuxer
@ -115,7 +115,7 @@ private:
CryptoSample ExtractCryptoSample(size_t aSampleSize,
java::sdk::CryptoInfo::LocalRef aCryptoInfo);
RefPtr<MediaRawData> ConvertToMediaRawData(java::GeckoHlsSample::LocalRef aSample);
RefPtr<MediaRawData> ConvertToMediaRawData(java::GeckoHLSSample::LocalRef aSample);
RefPtr<HLSDemuxer> mParent;
TrackInfo::TrackType mType;

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

@ -11,21 +11,21 @@ using namespace mozilla::java;
namespace mozilla {
HlsResourceCallbacksSupport::HlsResourceCallbacksSupport(HLSResource* aResource)
HLSResourceCallbacksSupport::HLSResourceCallbacksSupport(HLSResource* aResource)
{
MOZ_ASSERT(aResource);
mResource = aResource;
}
void
HlsResourceCallbacksSupport::OnDataArrived()
HLSResourceCallbacksSupport::OnDataArrived()
{
MOZ_ASSERT(mResource);
mResource->onDataAvailable();
}
void
HlsResourceCallbacksSupport::OnError(int aErrorCode)
HLSResourceCallbacksSupport::OnError(int aErrorCode)
{
MOZ_ASSERT(mResource);
}
@ -39,13 +39,13 @@ HLSResource::HLSResource(MediaResourceCallback* aCallback,
nsCString spec;
nsresult rv = aURI->GetSpec(spec);
(void)rv;
HlsResourceCallbacksSupport::Init();
mJavaCallbacks = GeckoHlsResourceWrapper::HlsResourceCallbacks::New();
HlsResourceCallbacksSupport::AttachNative(mJavaCallbacks,
mozilla::MakeUnique<HlsResourceCallbacksSupport>(this));
mHlsResourceWrapper = java::GeckoHlsResourceWrapper::Create(NS_ConvertUTF8toUTF16(spec),
HLSResourceCallbacksSupport::Init();
mJavaCallbacks = GeckoHLSResourceWrapper::Callbacks::New();
HLSResourceCallbacksSupport::AttachNative(mJavaCallbacks,
mozilla::MakeUnique<HLSResourceCallbacksSupport>(this));
mHLSResourceWrapper = java::GeckoHLSResourceWrapper::Create(NS_ConvertUTF8toUTF16(spec),
mJavaCallbacks);
MOZ_ASSERT(mHlsResourceWrapper);
MOZ_ASSERT(mHLSResourceWrapper);
}
void
@ -59,12 +59,12 @@ HLSResource::onDataAvailable()
HLSResource::~HLSResource()
{
if (mJavaCallbacks) {
HlsResourceCallbacksSupport::DisposeNative(mJavaCallbacks);
HLSResourceCallbacksSupport::DisposeNative(mJavaCallbacks);
mJavaCallbacks = nullptr;
}
if (mHlsResourceWrapper) {
mHlsResourceWrapper->Destroy();
mHlsResourceWrapper = nullptr;
if (mHLSResourceWrapper) {
mHLSResourceWrapper->Destroy();
mHLSResourceWrapper = nullptr;
}
HLS_DEBUG("HLSResource", "Destroy");
}

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

@ -20,15 +20,15 @@ namespace mozilla {
class HLSResource;
class HlsResourceCallbacksSupport
: public GeckoHlsResourceWrapper::HlsResourceCallbacks::Natives<HlsResourceCallbacksSupport>
class HLSResourceCallbacksSupport
: public GeckoHLSResourceWrapper::Callbacks::Natives<HLSResourceCallbacksSupport>
{
public:
typedef GeckoHlsResourceWrapper::HlsResourceCallbacks::Natives<HlsResourceCallbacksSupport> NativeCallbacks;
typedef GeckoHLSResourceWrapper::Callbacks::Natives<HLSResourceCallbacksSupport> NativeCallbacks;
using NativeCallbacks::DisposeNative;
using NativeCallbacks::AttachNative;
HlsResourceCallbacksSupport(HLSResource* aResource);
HLSResourceCallbacksSupport(HLSResource* aResource);
void OnDataArrived();
void OnError(int aErrorCode);
@ -98,12 +98,12 @@ public:
return false;
}
java::GeckoHlsResourceWrapper::GlobalRef GetResourceWrapper() {
return mHlsResourceWrapper;
java::GeckoHLSResourceWrapper::GlobalRef GetResourceWrapper() {
return mHLSResourceWrapper;
}
private:
friend class HlsResourceCallbacksSupport;
friend class HLSResourceCallbacksSupport;
void onDataAvailable();
@ -120,8 +120,8 @@ private:
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
java::GeckoHlsResourceWrapper::GlobalRef mHlsResourceWrapper;
java::GeckoHlsResourceWrapper::HlsResourceCallbacks::GlobalRef mJavaCallbacks;
java::GeckoHLSResourceWrapper::GlobalRef mHLSResourceWrapper;
java::GeckoHLSResourceWrapper::Callbacks::GlobalRef mJavaCallbacks;
};
} // namespace mozilla

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

@ -1107,6 +1107,8 @@ tags = webvtt
[test_trackevent.html]
skip-if = android_version == '22' # android(bug 1368010)
tags = webvtt
[test_webvtt_event_same_time.html]
tags = webvtt
[test_unseekable.html]
[test_video_to_canvas.html]
skip-if = toolkit == 'android' # android(bug 1232305), bugs 1320418,1347953,1347954,1348140,1348386

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

@ -0,0 +1,63 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset='utf-8'>
<title>WebVTT : cue's onenter/onexit event order </title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<div id="content">
</div>
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
var c1exit = false;
var c3enter = false;
function runTest() {
info("--- create video ---");
var video = document.createElement("video");
video.src = "seek.webm";
video.autoplay = true;
document.getElementById("content").appendChild(video);
var track = video.addTextTrack("subtitles", "A", "en");
track.mode = "showing";
var cue1 = new VTTCue(1, 2, "Test cue1");
var cue2 = new VTTCue(2, 3, "Test cue2");
track.addCue(cue1);
track.addCue(cue2);
cue1.onexit = function () {
cue1.onexit = null;
c1exit = true;
}
cue2.onenter = function () {
cue2.onenter = null;
ok(c1exit, "cue1 onexit event before than cue2 onenter");
video.pause();
SimpleTest.finish();
}
var cue3 = new VTTCue(1, 2, "Test cue3");
var cue4 = new VTTCue(1, 2, "Test cue4");
track.addCue(cue3);
track.addCue(cue4);
cue3.onenter = function () {
cue3.onenter = null;
c3enter = true;
}
cue4.onenter = function () {
cue4.onenter = null;
ok(c3enter, "cue3 onenter event before than cue4 onenter");
}
}
onload = runTest;
</script>
</body>
</html>

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

@ -1262,7 +1262,7 @@ mozInlineSpellChecker::ShouldSpellCheckNode(nsIEditor* aEditor,
{
MOZ_ASSERT(aNode);
if (!aNode->IsContent())
return true;
return false;
nsIContent *content = aNode->AsContent();

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

@ -81,8 +81,11 @@ mozInlineSpellWordUtil::Init(const nsWeakPtr& aWeakEditor)
}
static inline bool
IsTextNode(nsINode* aNode)
IsSpellCheckingTextNode(nsINode* aNode)
{
nsIContent *parent = aNode->GetParent();
if (parent && parent->IsAnyOfHTMLElements(nsGkAtoms::script, nsGkAtoms::style))
return false;
return aNode->IsNodeOfType(nsINode::eTEXT);
}
@ -132,7 +135,7 @@ static nsINode*
FindNextTextNode(nsINode* aNode, int32_t aOffset, nsINode* aRoot)
{
NS_PRECONDITION(aNode, "Null starting node?");
NS_ASSERTION(!IsTextNode(aNode), "FindNextTextNode should start with a non-text node");
NS_ASSERTION(!IsSpellCheckingTextNode(aNode), "FindNextTextNode should start with a non-text node");
nsINode* checkNode;
// Need to start at the aOffset'th child
@ -147,7 +150,7 @@ FindNextTextNode(nsINode* aNode, int32_t aOffset, nsINode* aRoot)
checkNode = aNode->GetNextNonChildNode(aRoot);
}
while (checkNode && !IsTextNode(checkNode)) {
while (checkNode && !IsSpellCheckingTextNode(checkNode)) {
checkNode = checkNode->GetNextNode(aRoot);
}
return checkNode;
@ -179,7 +182,7 @@ mozInlineSpellWordUtil::SetEnd(nsINode* aEndNode, int32_t aEndOffset)
InvalidateWords();
if (!IsTextNode(aEndNode)) {
if (!IsSpellCheckingTextNode(aEndNode)) {
// End at the start of the first text node after aEndNode/aEndOffset.
aEndNode = FindNextTextNode(aEndNode, aEndOffset, mRootNode);
aEndOffset = 0;
@ -193,7 +196,7 @@ mozInlineSpellWordUtil::SetPosition(nsINode* aNode, int32_t aOffset)
{
InvalidateWords();
if (!IsTextNode(aNode)) {
if (!IsSpellCheckingTextNode(aNode)) {
// Start at the start of the first text node after aNode/aOffset.
aNode = FindNextTextNode(aNode, aOffset, mRootNode);
aOffset = 0;
@ -345,463 +348,6 @@ mozInlineSpellWordUtil::MakeRange(NodeOffset aBegin, NodeOffset aEnd,
return NS_OK;
}
/*********** DOM text extraction ************/
// IsDOMWordSeparator
//
// Determines if the given character should be considered as a DOM Word
// separator. Basically, this is whitespace, although it could also have
// certain punctuation that we know ALWAYS breaks words. This is important.
// For example, we can't have any punctuation that could appear in a URL
// or email address in this, because those need to always fit into a single
// DOM word.
static bool
IsDOMWordSeparator(char16_t ch)
{
// simple spaces
if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')
return true;
// complex spaces - check only if char isn't ASCII (uncommon)
if (ch >= 0xA0 &&
(ch == 0x00A0 || // NO-BREAK SPACE
ch == 0x2002 || // EN SPACE
ch == 0x2003 || // EM SPACE
ch == 0x2009 || // THIN SPACE
ch == 0x3000)) // IDEOGRAPHIC SPACE
return true;
// otherwise not a space
return false;
}
static inline bool
IsBRElement(nsINode* aNode)
{
return aNode->IsHTMLElement(nsGkAtoms::br);
}
/**
* Given a TextNode, checks to see if there's a DOM word separator before
* aBeforeOffset within it. This function does not modify aSeparatorOffset when
* it returns false.
*
* @param aNode the TextNode to check.
* @param aBeforeOffset the offset in the TextNode before which we will search
* for the DOM separator. You can pass INT32_MAX to search the entire
* length of the string.
* @param aSeparatorOffset will be set to the offset of the first separator it
* encounters. Will not be written to if no separator is found.
* @returns True if it found a separator.
*/
static bool
TextNodeContainsDOMWordSeparator(nsINode* aNode,
int32_t aBeforeOffset,
int32_t* aSeparatorOffset)
{
// aNode is actually an nsIContent, since it's eTEXT
nsIContent* content = static_cast<nsIContent*>(aNode);
const nsTextFragment* textFragment = content->GetText();
NS_ASSERTION(textFragment, "Where is our text?");
for (int32_t i = std::min(aBeforeOffset, int32_t(textFragment->GetLength())) - 1; i >= 0; --i) {
if (IsDOMWordSeparator(textFragment->CharAt(i))) {
// Be greedy, find as many separators as we can
for (int32_t j = i - 1; j >= 0; --j) {
if (IsDOMWordSeparator(textFragment->CharAt(j))) {
i = j;
} else {
break;
}
}
*aSeparatorOffset = i;
return true;
}
}
return false;
}
/**
* Check if there's a DOM word separator before aBeforeOffset in this node.
* Always returns true if it's a BR element.
* aSeparatorOffset is set to the index of the first character in the last
* separator if any is found (0 for BR elements).
*
* This function does not modify aSeparatorOffset when it returns false.
*/
static bool
ContainsDOMWordSeparator(nsINode* aNode, int32_t aBeforeOffset,
int32_t* aSeparatorOffset)
{
if (IsBRElement(aNode)) {
*aSeparatorOffset = 0;
return true;
}
if (!IsTextNode(aNode))
return false;
return TextNodeContainsDOMWordSeparator(aNode, aBeforeOffset,
aSeparatorOffset);
}
static bool
IsBreakElement(nsINode* aNode)
{
if (!aNode->IsElement()) {
return false;
}
dom::Element *element = aNode->AsElement();
if (element->IsHTMLElement(nsGkAtoms::br))
return true;
// If we don't have a frame, we don't consider ourselves a break
// element. In particular, words can span us.
if (!element->GetPrimaryFrame())
return false;
// Anything that's not an inline element is a break element.
// XXXbz should replaced inlines be break elements, though?
return element->GetPrimaryFrame()->StyleDisplay()->mDisplay !=
StyleDisplay::Inline;
}
struct CheckLeavingBreakElementClosure {
bool mLeftBreakElement;
};
static void
CheckLeavingBreakElement(nsINode* aNode, void* aClosure)
{
CheckLeavingBreakElementClosure* cl =
static_cast<CheckLeavingBreakElementClosure*>(aClosure);
if (!cl->mLeftBreakElement && IsBreakElement(aNode)) {
cl->mLeftBreakElement = true;
}
}
void
mozInlineSpellWordUtil::NormalizeWord(nsSubstring& aWord)
{
nsAutoString result;
::NormalizeWord(aWord, 0, aWord.Length(), result);
aWord = result;
}
void
mozInlineSpellWordUtil::BuildSoftText()
{
// First we have to work backwards from mSoftStart to find a text node
// containing a DOM word separator, a non-inline-element
// boundary, or the hard start node. That's where we'll start building the
// soft string from.
nsINode* node = mSoftBegin.mNode;
int32_t firstOffsetInNode = 0;
int32_t checkBeforeOffset = mSoftBegin.mOffset;
while (node) {
if (ContainsDOMWordSeparator(node, checkBeforeOffset, &firstOffsetInNode)) {
if (node == mSoftBegin.mNode) {
// If we find a word separator on the first node, look at the preceding
// word on the text node as well.
int32_t newOffset = 0;
if (firstOffsetInNode > 0) {
// Try to find the previous word boundary in the current node. If
// we can't find one, start checking previous sibling nodes (if any
// adjacent ones exist) to see if we can find any text nodes with
// DOM word separators. We bail out as soon as we see a node that is
// not a text node, or we run out of previous sibling nodes. In the
// event that we simply cannot find any preceding word separator, the
// offset is set to 0, and the soft text beginning node is set to the
// "most previous" text node before the original starting node, or
// kept at the original starting node if no previous text nodes exist.
if (!ContainsDOMWordSeparator(node, firstOffsetInNode - 1,
&newOffset)) {
nsINode* prevNode = node->GetPreviousSibling();
while (prevNode && IsTextNode(prevNode)) {
mSoftBegin.mNode = prevNode;
if (TextNodeContainsDOMWordSeparator(prevNode, INT32_MAX,
&newOffset)) {
break;
}
prevNode = prevNode->GetPreviousSibling();
}
}
}
firstOffsetInNode = newOffset;
mSoftBegin.mOffset = newOffset;
}
break;
}
checkBeforeOffset = INT32_MAX;
if (IsBreakElement(node)) {
// Since GetPreviousContent follows tree *preorder*, we're about to traverse
// up out of 'node'. Since node induces breaks (e.g., it's a block),
// don't bother trying to look outside it, just stop now.
break;
}
// GetPreviousContent below expects mRootNode to be an ancestor of node.
if (!nsContentUtils::ContentIsDescendantOf(node, mRootNode)) {
break;
}
node = node->GetPreviousContent(mRootNode);
}
// Now build up the string moving forward through the DOM until we reach
// the soft end and *then* see a DOM word separator, a non-inline-element
// boundary, or the hard end node.
mSoftText.Truncate();
mSoftTextDOMMapping.Clear();
bool seenSoftEnd = false;
// Leave this outside the loop so large heap string allocations can be reused
// across iterations
while (node) {
if (node == mSoftEnd.mNode) {
seenSoftEnd = true;
}
bool exit = false;
if (IsTextNode(node)) {
nsIContent* content = static_cast<nsIContent*>(node);
NS_ASSERTION(content, "Where is our content?");
const nsTextFragment* textFragment = content->GetText();
NS_ASSERTION(textFragment, "Where is our text?");
int32_t lastOffsetInNode = textFragment->GetLength();
if (seenSoftEnd) {
// check whether we can stop after this
for (int32_t i = node == mSoftEnd.mNode ? mSoftEnd.mOffset : 0;
i < int32_t(textFragment->GetLength()); ++i) {
if (IsDOMWordSeparator(textFragment->CharAt(i))) {
exit = true;
// stop at the first separator after the soft end point
lastOffsetInNode = i;
break;
}
}
}
if (firstOffsetInNode < lastOffsetInNode) {
int32_t len = lastOffsetInNode - firstOffsetInNode;
mSoftTextDOMMapping.AppendElement(
DOMTextMapping(NodeOffset(node, firstOffsetInNode), mSoftText.Length(), len));
bool ok = textFragment->AppendTo(mSoftText, firstOffsetInNode, len,
mozilla::fallible);
if (!ok) {
// probably out of memory, remove from mSoftTextDOMMapping
mSoftTextDOMMapping.RemoveElementAt(mSoftTextDOMMapping.Length() - 1);
exit = true;
}
}
firstOffsetInNode = 0;
}
if (exit)
break;
CheckLeavingBreakElementClosure closure = { false };
node = FindNextNode(node, mRootNode, CheckLeavingBreakElement, &closure);
if (closure.mLeftBreakElement || (node && IsBreakElement(node))) {
// We left, or are entering, a break element (e.g., block). Maybe we can
// stop now.
if (seenSoftEnd)
break;
// Record the break
mSoftText.Append(' ');
}
}
#ifdef DEBUG_SPELLCHECK
printf("Got DOM string: %s\n", NS_ConvertUTF16toUTF8(mSoftText).get());
#endif
}
nsresult
mozInlineSpellWordUtil::BuildRealWords()
{
// This is pretty simple. We just have to walk mSoftText, tokenizing it
// into "real words".
// We do an outer traversal of words delimited by IsDOMWordSeparator, calling
// SplitDOMWord on each of those DOM words
int32_t wordStart = -1;
mRealWords.Clear();
for (int32_t i = 0; i < int32_t(mSoftText.Length()); ++i) {
if (IsDOMWordSeparator(mSoftText.CharAt(i))) {
if (wordStart >= 0) {
nsresult rv = SplitDOMWord(wordStart, i);
if (NS_FAILED(rv)) {
return rv;
}
wordStart = -1;
}
} else {
if (wordStart < 0) {
wordStart = i;
}
}
}
if (wordStart >= 0) {
nsresult rv = SplitDOMWord(wordStart, mSoftText.Length());
if (NS_FAILED(rv)) {
return rv;
}
}
return NS_OK;
}
/*********** DOM/realwords<->mSoftText mapping functions ************/
int32_t
mozInlineSpellWordUtil::MapDOMPositionToSoftTextOffset(NodeOffset aNodeOffset)
{
if (!mSoftTextValid) {
NS_ERROR("Soft text must be valid if we're to map into it");
return -1;
}
for (int32_t i = 0; i < int32_t(mSoftTextDOMMapping.Length()); ++i) {
const DOMTextMapping& map = mSoftTextDOMMapping[i];
if (map.mNodeOffset.mNode == aNodeOffset.mNode) {
// Allow offsets at either end of the string, in particular, allow the
// offset that's at the end of the contributed string
int32_t offsetInContributedString =
aNodeOffset.mOffset - map.mNodeOffset.mOffset;
if (offsetInContributedString >= 0 &&
offsetInContributedString <= map.mLength)
return map.mSoftTextOffset + offsetInContributedString;
return -1;
}
}
return -1;
}
namespace {
template<class T>
class FirstLargerOffset
{
int32_t mSoftTextOffset;
public:
explicit FirstLargerOffset(int32_t aSoftTextOffset) : mSoftTextOffset(aSoftTextOffset) {}
int operator()(const T& t) const {
// We want the first larger offset, so never return 0 (which would
// short-circuit evaluation before finding the last such offset).
return mSoftTextOffset < t.mSoftTextOffset ? -1 : 1;
}
};
template<class T>
bool
FindLastNongreaterOffset(const nsTArray<T>& aContainer, int32_t aSoftTextOffset, size_t* aIndex)
{
if (aContainer.Length() == 0) {
return false;
}
BinarySearchIf(aContainer, 0, aContainer.Length(),
FirstLargerOffset<T>(aSoftTextOffset), aIndex);
if (*aIndex > 0) {
// There was at least one mapping with offset <= aSoftTextOffset. Step back
// to find the last element with |mSoftTextOffset <= aSoftTextOffset|.
*aIndex -= 1;
} else {
// Every mapping had offset greater than aSoftTextOffset.
MOZ_ASSERT(aContainer[*aIndex].mSoftTextOffset > aSoftTextOffset);
}
return true;
}
} // namespace
mozInlineSpellWordUtil::NodeOffset
mozInlineSpellWordUtil::MapSoftTextOffsetToDOMPosition(int32_t aSoftTextOffset,
DOMMapHint aHint)
{
NS_ASSERTION(mSoftTextValid, "Soft text must be valid if we're to map out of it");
if (!mSoftTextValid)
return NodeOffset(nullptr, -1);
// Find the last mapping, if any, such that mSoftTextOffset <= aSoftTextOffset
size_t index;
bool found = FindLastNongreaterOffset(mSoftTextDOMMapping, aSoftTextOffset, &index);
if (!found) {
return NodeOffset(nullptr, -1);
}
// 'index' is now the last mapping, if any, such that
// mSoftTextOffset <= aSoftTextOffset.
// If we're doing HINT_END, then we may want to return the end of the
// the previous mapping instead of the start of this mapping
if (aHint == HINT_END && index > 0) {
const DOMTextMapping& map = mSoftTextDOMMapping[index - 1];
if (map.mSoftTextOffset + map.mLength == aSoftTextOffset)
return NodeOffset(map.mNodeOffset.mNode, map.mNodeOffset.mOffset + map.mLength);
}
// We allow ourselves to return the end of this mapping even if we're
// doing HINT_START. This will only happen if there is no mapping which this
// point is the start of. I'm not 100% sure this is OK...
const DOMTextMapping& map = mSoftTextDOMMapping[index];
int32_t offset = aSoftTextOffset - map.mSoftTextOffset;
if (offset >= 0 && offset <= map.mLength)
return NodeOffset(map.mNodeOffset.mNode, map.mNodeOffset.mOffset + offset);
return NodeOffset(nullptr, -1);
}
int32_t
mozInlineSpellWordUtil::FindRealWordContaining(int32_t aSoftTextOffset,
DOMMapHint aHint, bool aSearchForward)
{
NS_ASSERTION(mSoftTextValid, "Soft text must be valid if we're to map out of it");
if (!mSoftTextValid)
return -1;
// Find the last word, if any, such that mSoftTextOffset <= aSoftTextOffset
size_t index;
bool found = FindLastNongreaterOffset(mRealWords, aSoftTextOffset, &index);
if (!found) {
return -1;
}
// 'index' is now the last word, if any, such that
// mSoftTextOffset <= aSoftTextOffset.
// If we're doing HINT_END, then we may want to return the end of the
// the previous word instead of the start of this word
if (aHint == HINT_END && index > 0) {
const RealWord& word = mRealWords[index - 1];
if (word.mSoftTextOffset + word.mLength == aSoftTextOffset)
return index - 1;
}
// We allow ourselves to return the end of this word even if we're
// doing HINT_START. This will only happen if there is no word which this
// point is the start of. I'm not 100% sure this is OK...
const RealWord& word = mRealWords[index];
int32_t offset = aSoftTextOffset - word.mSoftTextOffset;
if (offset >= 0 && offset <= static_cast<int32_t>(word.mLength))
return index;
if (aSearchForward) {
if (mRealWords[0].mSoftTextOffset > aSoftTextOffset) {
// All words have mSoftTextOffset > aSoftTextOffset
return 0;
}
// 'index' is the last word such that mSoftTextOffset <= aSoftTextOffset.
// Word index+1, if it exists, will be the first with
// mSoftTextOffset > aSoftTextOffset.
if (index + 1 < mRealWords.Length())
return index + 1;
}
return -1;
}
/*********** Word Splitting ************/
// classifies a given character in the DOM word
@ -1043,6 +589,472 @@ WordSplitState::ShouldSkipWord(int32_t aStart, int32_t aLength)
return false;
}
/*********** DOM text extraction ************/
// IsDOMWordSeparator
//
// Determines if the given character should be considered as a DOM Word
// separator. Basically, this is whitespace, although it could also have
// certain punctuation that we know ALWAYS breaks words. This is important.
// For example, we can't have any punctuation that could appear in a URL
// or email address in this, because those need to always fit into a single
// DOM word.
static bool
IsDOMWordSeparator(char16_t ch)
{
// simple spaces
if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')
return true;
// complex spaces - check only if char isn't ASCII (uncommon)
if (ch >= 0xA0 &&
(ch == 0x00A0 || // NO-BREAK SPACE
ch == 0x2002 || // EN SPACE
ch == 0x2003 || // EM SPACE
ch == 0x2009 || // THIN SPACE
ch == 0x3000)) // IDEOGRAPHIC SPACE
return true;
// otherwise not a space
return false;
}
static inline bool
IsBRElement(nsINode* aNode)
{
return aNode->IsHTMLElement(nsGkAtoms::br);
}
/**
* Given a TextNode, checks to see if there's a DOM word separator before
* aBeforeOffset within it. This function does not modify aSeparatorOffset when
* it returns false.
*
* @param aNode the TextNode to check.
* @param aBeforeOffset the offset in the TextNode before which we will search
* for the DOM separator. You can pass INT32_MAX to search the entire
* length of the string.
* @param aSeparatorOffset will be set to the offset of the first separator it
* encounters. Will not be written to if no separator is found.
* @returns True if it found a separator.
*/
static bool
TextNodeContainsDOMWordSeparator(nsINode* aNode,
int32_t aBeforeOffset,
int32_t* aSeparatorOffset)
{
// aNode is actually an nsIContent, since it's eTEXT
nsIContent* content = static_cast<nsIContent*>(aNode);
const nsTextFragment* textFragment = content->GetText();
NS_ASSERTION(textFragment, "Where is our text?");
nsString text;
int32_t end = std::min(aBeforeOffset, int32_t(textFragment->GetLength()));
bool ok = textFragment->AppendTo(text, 0, end, mozilla::fallible);
if(!ok)
return false;
WordSplitState state(nullptr, text, 0, end);
for (int32_t i = end - 1; i >= 0; --i) {
if (IsDOMWordSeparator(textFragment->CharAt(i)) ||
state.ClassifyCharacter(i, true) == CHAR_CLASS_SEPARATOR) {
// Be greedy, find as many separators as we can
for (int32_t j = i - 1; j >= 0; --j) {
if (IsDOMWordSeparator(textFragment->CharAt(j)) ||
state.ClassifyCharacter(j, true) == CHAR_CLASS_SEPARATOR) {
i = j;
} else {
break;
}
}
*aSeparatorOffset = i;
return true;
}
}
return false;
}
/**
* Check if there's a DOM word separator before aBeforeOffset in this node.
* Always returns true if it's a BR element.
* aSeparatorOffset is set to the index of the first character in the last
* separator if any is found (0 for BR elements).
*
* This function does not modify aSeparatorOffset when it returns false.
*/
static bool
ContainsDOMWordSeparator(nsINode* aNode, int32_t aBeforeOffset,
int32_t* aSeparatorOffset)
{
if (IsBRElement(aNode)) {
*aSeparatorOffset = 0;
return true;
}
if (!IsSpellCheckingTextNode(aNode))
return false;
return TextNodeContainsDOMWordSeparator(aNode, aBeforeOffset,
aSeparatorOffset);
}
static bool
IsBreakElement(nsINode* aNode)
{
if (!aNode->IsElement()) {
return false;
}
dom::Element *element = aNode->AsElement();
if (element->IsHTMLElement(nsGkAtoms::br))
return true;
// If we don't have a frame, we don't consider ourselves a break
// element. In particular, words can span us.
if (!element->GetPrimaryFrame())
return false;
// Anything that's not an inline element is a break element.
// XXXbz should replaced inlines be break elements, though?
return element->GetPrimaryFrame()->StyleDisplay()->mDisplay !=
StyleDisplay::Inline;
}
struct CheckLeavingBreakElementClosure {
bool mLeftBreakElement;
};
static void
CheckLeavingBreakElement(nsINode* aNode, void* aClosure)
{
CheckLeavingBreakElementClosure* cl =
static_cast<CheckLeavingBreakElementClosure*>(aClosure);
if (!cl->mLeftBreakElement && IsBreakElement(aNode)) {
cl->mLeftBreakElement = true;
}
}
void
mozInlineSpellWordUtil::NormalizeWord(nsSubstring& aWord)
{
nsAutoString result;
::NormalizeWord(aWord, 0, aWord.Length(), result);
aWord = result;
}
void
mozInlineSpellWordUtil::BuildSoftText()
{
// First we have to work backwards from mSoftStart to find a text node
// containing a DOM word separator, a non-inline-element
// boundary, or the hard start node. That's where we'll start building the
// soft string from.
nsINode* node = mSoftBegin.mNode;
int32_t firstOffsetInNode = 0;
int32_t checkBeforeOffset = mSoftBegin.mOffset;
while (node) {
if (ContainsDOMWordSeparator(node, checkBeforeOffset, &firstOffsetInNode)) {
if (node == mSoftBegin.mNode) {
// If we find a word separator on the first node, look at the preceding
// word on the text node as well.
int32_t newOffset = 0;
if (firstOffsetInNode > 0) {
// Try to find the previous word boundary in the current node. If
// we can't find one, start checking previous sibling nodes (if any
// adjacent ones exist) to see if we can find any text nodes with
// DOM word separators. We bail out as soon as we see a node that is
// not a text node, or we run out of previous sibling nodes. In the
// event that we simply cannot find any preceding word separator, the
// offset is set to 0, and the soft text beginning node is set to the
// "most previous" text node before the original starting node, or
// kept at the original starting node if no previous text nodes exist.
if (!ContainsDOMWordSeparator(node, firstOffsetInNode - 1,
&newOffset)) {
nsINode* prevNode = node->GetPreviousSibling();
while (prevNode && IsSpellCheckingTextNode(prevNode)) {
mSoftBegin.mNode = prevNode;
if (TextNodeContainsDOMWordSeparator(prevNode, INT32_MAX,
&newOffset)) {
break;
}
prevNode = prevNode->GetPreviousSibling();
}
}
}
firstOffsetInNode = newOffset;
mSoftBegin.mOffset = newOffset;
}
break;
}
checkBeforeOffset = INT32_MAX;
if (IsBreakElement(node)) {
// Since GetPreviousContent follows tree *preorder*, we're about to traverse
// up out of 'node'. Since node induces breaks (e.g., it's a block),
// don't bother trying to look outside it, just stop now.
break;
}
// GetPreviousContent below expects mRootNode to be an ancestor of node.
if (!nsContentUtils::ContentIsDescendantOf(node, mRootNode)) {
break;
}
node = node->GetPreviousContent(mRootNode);
}
// Now build up the string moving forward through the DOM until we reach
// the soft end and *then* see a DOM word separator, a non-inline-element
// boundary, or the hard end node.
mSoftText.Truncate();
mSoftTextDOMMapping.Clear();
bool seenSoftEnd = false;
// Leave this outside the loop so large heap string allocations can be reused
// across iterations
while (node) {
if (node == mSoftEnd.mNode) {
seenSoftEnd = true;
}
bool exit = false;
if (IsSpellCheckingTextNode(node)) {
nsIContent* content = static_cast<nsIContent*>(node);
NS_ASSERTION(content, "Where is our content?");
const nsTextFragment* textFragment = content->GetText();
NS_ASSERTION(textFragment, "Where is our text?");
int32_t lastOffsetInNode = textFragment->GetLength();
if (seenSoftEnd) {
// check whether we can stop after this
for (int32_t i = node == mSoftEnd.mNode ? mSoftEnd.mOffset : 0;
i < int32_t(textFragment->GetLength()); ++i) {
if (IsDOMWordSeparator(textFragment->CharAt(i))) {
exit = true;
// stop at the first separator after the soft end point
lastOffsetInNode = i;
break;
}
}
}
if (firstOffsetInNode < lastOffsetInNode) {
int32_t len = lastOffsetInNode - firstOffsetInNode;
mSoftTextDOMMapping.AppendElement(
DOMTextMapping(NodeOffset(node, firstOffsetInNode), mSoftText.Length(), len));
bool ok = textFragment->AppendTo(mSoftText, firstOffsetInNode, len,
mozilla::fallible);
if (!ok) {
// probably out of memory, remove from mSoftTextDOMMapping
mSoftTextDOMMapping.RemoveElementAt(mSoftTextDOMMapping.Length() - 1);
exit = true;
}
}
firstOffsetInNode = 0;
}
if (exit)
break;
CheckLeavingBreakElementClosure closure = { false };
node = FindNextNode(node, mRootNode, CheckLeavingBreakElement, &closure);
if (closure.mLeftBreakElement || (node && IsBreakElement(node))) {
// We left, or are entering, a break element (e.g., block). Maybe we can
// stop now.
if (seenSoftEnd)
break;
// Record the break
mSoftText.Append(' ');
}
}
#ifdef DEBUG_SPELLCHECK
printf("Got DOM string: %s\n", NS_ConvertUTF16toUTF8(mSoftText).get());
#endif
}
nsresult
mozInlineSpellWordUtil::BuildRealWords()
{
// This is pretty simple. We just have to walk mSoftText, tokenizing it
// into "real words".
// We do an outer traversal of words delimited by IsDOMWordSeparator, calling
// SplitDOMWord on each of those DOM words
int32_t wordStart = -1;
mRealWords.Clear();
for (int32_t i = 0; i < int32_t(mSoftText.Length()); ++i) {
if (IsDOMWordSeparator(mSoftText.CharAt(i))) {
if (wordStart >= 0) {
nsresult rv = SplitDOMWord(wordStart, i);
if (NS_FAILED(rv)) {
return rv;
}
wordStart = -1;
}
} else {
if (wordStart < 0) {
wordStart = i;
}
}
}
if (wordStart >= 0) {
nsresult rv = SplitDOMWord(wordStart, mSoftText.Length());
if (NS_FAILED(rv)) {
return rv;
}
}
return NS_OK;
}
/*********** DOM/realwords<->mSoftText mapping functions ************/
int32_t
mozInlineSpellWordUtil::MapDOMPositionToSoftTextOffset(NodeOffset aNodeOffset)
{
if (!mSoftTextValid) {
NS_ERROR("Soft text must be valid if we're to map into it");
return -1;
}
for (int32_t i = 0; i < int32_t(mSoftTextDOMMapping.Length()); ++i) {
const DOMTextMapping& map = mSoftTextDOMMapping[i];
if (map.mNodeOffset.mNode == aNodeOffset.mNode) {
// Allow offsets at either end of the string, in particular, allow the
// offset that's at the end of the contributed string
int32_t offsetInContributedString =
aNodeOffset.mOffset - map.mNodeOffset.mOffset;
if (offsetInContributedString >= 0 &&
offsetInContributedString <= map.mLength)
return map.mSoftTextOffset + offsetInContributedString;
return -1;
}
}
return -1;
}
namespace {
template<class T>
class FirstLargerOffset
{
int32_t mSoftTextOffset;
public:
explicit FirstLargerOffset(int32_t aSoftTextOffset) : mSoftTextOffset(aSoftTextOffset) {}
int operator()(const T& t) const {
// We want the first larger offset, so never return 0 (which would
// short-circuit evaluation before finding the last such offset).
return mSoftTextOffset < t.mSoftTextOffset ? -1 : 1;
}
};
template<class T>
bool
FindLastNongreaterOffset(const nsTArray<T>& aContainer, int32_t aSoftTextOffset, size_t* aIndex)
{
if (aContainer.Length() == 0) {
return false;
}
BinarySearchIf(aContainer, 0, aContainer.Length(),
FirstLargerOffset<T>(aSoftTextOffset), aIndex);
if (*aIndex > 0) {
// There was at least one mapping with offset <= aSoftTextOffset. Step back
// to find the last element with |mSoftTextOffset <= aSoftTextOffset|.
*aIndex -= 1;
} else {
// Every mapping had offset greater than aSoftTextOffset.
MOZ_ASSERT(aContainer[*aIndex].mSoftTextOffset > aSoftTextOffset);
}
return true;
}
} // namespace
mozInlineSpellWordUtil::NodeOffset
mozInlineSpellWordUtil::MapSoftTextOffsetToDOMPosition(int32_t aSoftTextOffset,
DOMMapHint aHint)
{
NS_ASSERTION(mSoftTextValid, "Soft text must be valid if we're to map out of it");
if (!mSoftTextValid)
return NodeOffset(nullptr, -1);
// Find the last mapping, if any, such that mSoftTextOffset <= aSoftTextOffset
size_t index;
bool found = FindLastNongreaterOffset(mSoftTextDOMMapping, aSoftTextOffset, &index);
if (!found) {
return NodeOffset(nullptr, -1);
}
// 'index' is now the last mapping, if any, such that
// mSoftTextOffset <= aSoftTextOffset.
// If we're doing HINT_END, then we may want to return the end of the
// the previous mapping instead of the start of this mapping
if (aHint == HINT_END && index > 0) {
const DOMTextMapping& map = mSoftTextDOMMapping[index - 1];
if (map.mSoftTextOffset + map.mLength == aSoftTextOffset)
return NodeOffset(map.mNodeOffset.mNode, map.mNodeOffset.mOffset + map.mLength);
}
// We allow ourselves to return the end of this mapping even if we're
// doing HINT_START. This will only happen if there is no mapping which this
// point is the start of. I'm not 100% sure this is OK...
const DOMTextMapping& map = mSoftTextDOMMapping[index];
int32_t offset = aSoftTextOffset - map.mSoftTextOffset;
if (offset >= 0 && offset <= map.mLength)
return NodeOffset(map.mNodeOffset.mNode, map.mNodeOffset.mOffset + offset);
return NodeOffset(nullptr, -1);
}
int32_t
mozInlineSpellWordUtil::FindRealWordContaining(int32_t aSoftTextOffset,
DOMMapHint aHint, bool aSearchForward)
{
NS_ASSERTION(mSoftTextValid, "Soft text must be valid if we're to map out of it");
if (!mSoftTextValid)
return -1;
// Find the last word, if any, such that mSoftTextOffset <= aSoftTextOffset
size_t index;
bool found = FindLastNongreaterOffset(mRealWords, aSoftTextOffset, &index);
if (!found) {
return -1;
}
// 'index' is now the last word, if any, such that
// mSoftTextOffset <= aSoftTextOffset.
// If we're doing HINT_END, then we may want to return the end of the
// the previous word instead of the start of this word
if (aHint == HINT_END && index > 0) {
const RealWord& word = mRealWords[index - 1];
if (word.mSoftTextOffset + word.mLength == aSoftTextOffset)
return index - 1;
}
// We allow ourselves to return the end of this word even if we're
// doing HINT_START. This will only happen if there is no word which this
// point is the start of. I'm not 100% sure this is OK...
const RealWord& word = mRealWords[index];
int32_t offset = aSoftTextOffset - word.mSoftTextOffset;
if (offset >= 0 && offset <= static_cast<int32_t>(word.mLength))
return index;
if (aSearchForward) {
if (mRealWords[0].mSoftTextOffset > aSoftTextOffset) {
// All words have mSoftTextOffset > aSoftTextOffset
return 0;
}
// 'index' is the last word such that mSoftTextOffset <= aSoftTextOffset.
// Word index+1, if it exists, will be the first with
// mSoftTextOffset > aSoftTextOffset.
if (index + 1 < mRealWords.Length())
return index + 1;
}
return -1;
}
// mozInlineSpellWordUtil::SplitDOMWord
nsresult

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

@ -584,8 +584,9 @@ DisplayListBuilder::PushClip(const WrRect& aClipRect,
const WrImageMask* aMask)
{
uint64_t clip_id = wr_dp_push_clip(mWrState, aClipRect, aMask);
WRDL_LOG("PushClip id=%" PRIu64 " r=%s m=%p\n", clip_id,
Stringify(aClipRect).c_str(), aMask);
WRDL_LOG("PushClip id=%" PRIu64 " r=%s m=%p b=%s\n", clip_id,
Stringify(aClipRect).c_str(), aMask,
aMask ? Stringify(aMask->rect).c_str() : "none");
mClipIdStack.push_back(clip_id);
}
@ -868,7 +869,8 @@ WrClipRegionToken
DisplayListBuilder::PushClipRegion(const WrRect& aMain,
const WrImageMask* aMask)
{
WRDL_LOG("PushClipRegion r=%s m=%p\n", Stringify(aMain).c_str(), aMask);
WRDL_LOG("PushClipRegion r=%s m=%p b=%s\n", Stringify(aMain).c_str(), aMask,
aMask ? Stringify(aMask->rect).c_str() : "none");
return wr_dp_push_clip_region(mWrState,
aMain,
nullptr, 0,
@ -880,8 +882,9 @@ DisplayListBuilder::PushClipRegion(const WrRect& aMain,
const nsTArray<WrComplexClipRegion>& aComplex,
const WrImageMask* aMask)
{
WRDL_LOG("PushClipRegion r=%s cl=%d m=%p\n", Stringify(aMain).c_str(),
(int)aComplex.Length(), aMask);
WRDL_LOG("PushClipRegion r=%s cl=%d m=%p b=%s\n", Stringify(aMain).c_str(),
(int)aComplex.Length(), aMask,
aMask ? Stringify(aMask->rect).c_str() : "none");
return wr_dp_push_clip_region(mWrState,
aMain,
aComplex.Elements(), aComplex.Length(),

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

@ -1361,14 +1361,24 @@ pub extern "C" fn wr_dp_pop_stacking_context(state: &mut WrState) {
#[no_mangle]
pub extern "C" fn wr_dp_push_clip(state: &mut WrState,
clip_rect: WrRect,
rect: WrRect,
mask: *const WrImageMask)
-> u64 {
assert!(unsafe { is_in_main_thread() });
let clip_rect = clip_rect.into();
let mask = unsafe { mask.as_ref() }.map(|x| x.into());
let content_rect: LayoutRect = rect.into();
// Both the clip rect and mask rect need to be relative to the
// content rect when the clip region is being used as part of a clip item.
// Since the clip_rect is the same as the content_rect we can just set the
// origin to zero.
let clip_rect = LayoutRect::new(LayoutPoint::zero(), content_rect.size);
let mut mask : Option<ImageMask> = unsafe { mask.as_ref() }.map(|x| x.into());
if let Some(ref mut m) = mask {
m.rect.origin = m.rect.origin - content_rect.origin;
}
let clip_region = state.frame_builder.dl_builder.push_clip_region(&clip_rect, vec![], mask);
let clip_id = state.frame_builder.dl_builder.define_clip(clip_rect, clip_region, None);
let clip_id = state.frame_builder.dl_builder.define_clip(content_rect, clip_region, None);
state.frame_builder.dl_builder.push_clip_id(clip_id);
// return the u64 id value from inside the ClipId::Clip(..)
match clip_id {
@ -1396,8 +1406,14 @@ pub extern "C" fn wr_dp_push_scroll_layer(state: &mut WrState,
// Avoid defining multiple scroll clips with the same clip id, as that
// results in undefined behaviour or assertion failures.
if !state.frame_builder.scroll_clips_defined.contains(&clip_id) {
let content_rect = content_rect.into();
let clip_rect = clip_rect.into();
let content_rect: LayoutRect = content_rect.into();
// Both the clip rect and mask rect need to be relative to the
// content_rect when the clip region is being used as part of a clip
// item. In this case there is no mask rect so that's a no-op.
let mut clip_rect: LayoutRect = clip_rect.into();
clip_rect.origin = clip_rect.origin - content_rect.origin;
let clip_region = state.frame_builder.dl_builder.push_clip_region(&clip_rect, vec![], None);
state.frame_builder.dl_builder.define_clip(content_rect, clip_region, Some(clip_id));
state.frame_builder.scroll_clips_defined.insert(clip_id);

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

@ -739,7 +739,7 @@ WR_FUNC;
WR_INLINE
uint64_t wr_dp_push_clip(WrState *aState,
WrRect aClipRect,
WrRect aRect,
const WrImageMask *aMask)
WR_FUNC;

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

@ -63,7 +63,20 @@ ServoRestyleManager::PostRestyleEvent(Element* aElement,
mHaveNonAnimationRestyles = true;
}
Servo_NoteExplicitHints(aElement, aRestyleHint, aMinChangeHint);
if (aRestyleHint & eRestyle_LaterSiblings) {
aRestyleHint &= ~eRestyle_LaterSiblings;
nsRestyleHint siblingHint = eRestyle_Subtree;
Element* current = aElement->GetNextElementSibling();
while (current) {
Servo_NoteExplicitHints(current, siblingHint, nsChangeHint(0));
current = current->GetNextElementSibling();
}
}
if (aRestyleHint || aMinChangeHint) {
Servo_NoteExplicitHints(aElement, aRestyleHint, aMinChangeHint);
}
}
void
@ -519,20 +532,21 @@ ServoRestyleManager::SnapshotFor(Element* aElement)
}
/* static */ nsIFrame*
ServoRestyleManager::FrameForPseudoElement(const nsIContent* aContent,
ServoRestyleManager::FrameForPseudoElement(const Element* aElement,
nsIAtom* aPseudoTagOrNull)
{
MOZ_ASSERT(!aPseudoTagOrNull || aContent->IsElement());
if (!aPseudoTagOrNull) {
return aContent->GetPrimaryFrame();
return nsLayoutUtils::GetStyleFrame(aElement);
}
if (aPseudoTagOrNull == nsCSSPseudoElements::before) {
return nsLayoutUtils::GetBeforeFrame(aContent);
Element* pseudoElement = nsLayoutUtils::GetBeforePseudo(aElement);
return pseudoElement ? nsLayoutUtils::GetStyleFrame(pseudoElement) : nullptr;
}
if (aPseudoTagOrNull == nsCSSPseudoElements::after) {
return nsLayoutUtils::GetAfterFrame(aContent);
Element* pseudoElement = nsLayoutUtils::GetAfterPseudo(aElement);
return pseudoElement ? nsLayoutUtils::GetStyleFrame(pseudoElement) : nullptr;
}
if (aPseudoTagOrNull == nsCSSPseudoElements::firstLine ||
@ -787,7 +801,7 @@ ServoRestyleManager::AttributeWillChange(Element* aElement,
}
ServoElementSnapshot& snapshot = SnapshotFor(aElement);
snapshot.AddAttrs(aElement);
snapshot.AddAttrs(aElement, aNameSpaceID, aAttribute);
if (AttributeInfluencesOtherPseudoClassState(aElement, aAttribute)) {
snapshot.AddOtherPseudoClassState(aElement);

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

@ -82,7 +82,7 @@ public:
* Right now only supports a null tag, before or after. If the pseudo-element
* is not null, the content needs to be an element.
*/
static nsIFrame* FrameForPseudoElement(const nsIContent* aContent,
static nsIFrame* FrameForPseudoElement(const Element* aElement,
nsIAtom* aPseudoTagOrNull);
/**

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

@ -17,3 +17,4 @@ fuzzy-if(OSX==1010,1,10) == quotes-001.xml quotes-001-ref.xml
fuzzy-if(OSX==1010,1,10) == table-ignoring-whitespace-01.html table-ignoring-whitespace-01-ref.html
fuzzy-if(OSX==1010,1,10) == table-parts-01.html table-parts-01-ref.html
== before-style-sharing.html before-style-sharing-ref.html
== transitive-style-invalidation.html transitive-style-invalidation-ref.html

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

@ -0,0 +1,19 @@
<!doctype html>
<style>
.foo > div::before {
display: block;
width: 100px;
height: 100px;
background: green;
content: "";
}
</style>
<div class="foo">
<div>
</div>
</div>
<script>
onload = function() {
document.querySelector('.foo').className = 'foo bar';
}
</script>

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

@ -0,0 +1,22 @@
<!doctype html>
<style>
.foo > div::before {
display: block;
width: 100px;
height: 100px;
background: red;
content: "";
}
.foo.bar > div::before {
background: green;
}
</style>
<div class="foo">
<div>
</div>
</div>
<script>
onload = function() {
document.querySelector('.foo').className = 'foo bar';
}
</script>

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

@ -12,39 +12,39 @@
<!-- test the clip is not too big (or ignored altogether) -->
<rect width="100%" height="100%" fill="red"
clip-path="polygon(100px 100px,200px 100px,200px 200px,100px 200px)"/>
clip-path="polygon(100px 100px, 200px 100px, 200px 200px, 100px 200px)"/>
<rect x="98" y="98" width="105" height="105" fill="lime"/>
<!-- test the clip does not simply make the element not render -->
<rect x="300" y="100" width="100" height="100" fill="red"/>
<rect x="280" y="80" width="150" height="150" fill="lime"
clip-path="polygon(20px 20px,120px 20px,120px 120px,20px 120px)"/>
clip-path="polygon(20px 20px, 120px 20px, 120px 120px, 20px 120px)"/>
<!-- percentage values -->
<svg x="100" y="300" width="100" height="100">
<rect width="100%" height="100%" fill="red"
clip-path="polygon(0 0,50% 0,50% 50%,0 50%)"/>
clip-path="polygon(0 0, 50% 0, 50% 50%, 0 50%)"/>
<rect width="55" height="55" fill="lime"/>
</svg>
<!-- mixed absolute and percentage values -->
<svg x="300" y="300" width="100" height="100">
<rect width="100%" height="100%" fill="red"
clip-path="polygon(0 0,50% 0,50px 50%,0 50px)"/>
clip-path="polygon(0 0, 50% 0, 50px 50%, 0 50px)"/>
<rect width="55" height="55" fill="lime"/>
</svg>
<!-- mixed other units -->
<svg x="500" y="300" width="100" height="100">
<rect width="100%" height="100%" fill="red"
clip-path="polygon(0 0,5em 0,5em 10%,0 10px)"/>
clip-path="polygon(0 0, 5em 0, 5em 10%, 0 10px)"/>
<rect width="5em" height="10%" fill="lime"/>
</svg>
<!-- check fill-rule and clip-rule are ignored for polygon clip-path -->
<svg x="500" y="100" width="100" height="100" fill-rule="evenodd" clip-rule="evenodd">
<rect width="100%" height="100%" fill="red"
clip-path="polygon(0 0,50px 0,50px 50px,0 50px,0 0,50px 0,50px 50px,0 50px,0 0)"/>
clip-path="polygon(0 0, 50px 0, 50px 50px, 0 50px, 0 0, 50px 0, 50px 50px, 0 50px, 0 0)"/>
<rect width="55" height="55" fill="lime"/>
</svg>

До

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

После

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

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

@ -12,10 +12,10 @@
<!-- test elementFromPoint can get the element using clip-path -->
<rect id="in" x="100" y="100" width="100" height="100"
clip-path="polygon(0 0,50px 0,50px 50px,0 50px)"/>
clip-path="polygon(0 0, 50px 0, 50px 50px, 0 50px)"/>
<script>
function testElementFromPoint() {
let inCount = outCount = 0, inElem, outElem;
let inCount = 0, outCount = 0, inElem, outElem;
document.getElementById("in").style.fill = "red";
document.getElementById("out").style.fill = "blue";

До

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

После

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

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

@ -0,0 +1,9 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS test reference</title>
<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
<style>
div { color: green; }
</style>
<div>This should be green</div>
<div>And this too</div>

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

@ -0,0 +1,24 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Test: [id] and [class] attribute selectors are invalidated correctly.</title>
<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
<link rel="help" href="https://drafts.csswg.org/selectors-4/#attribute-selectors">
<link rel="match" href="class-id-attr-selector-invalidation-01-ref.html">
<style>
[class="foo"] {
color: green;
}
[id="baz"] {
color: green;
}
</style>
<div id="foo">This should be green</div>
<div id="bar">And this too</div>
<script>
onload = function() {
document.documentElement.offsetTop;
foo.classList.add("foo");
bar.setAttribute("id", "baz");
document.documentElement.offsetTop;
}
</script>

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

@ -9,3 +9,4 @@ needs-focus == focus-within-3.html focus-within-3-ref.html
== dir-style-03b.html dir-style-03-ref.html
== dir-style-04.html dir-style-04-ref.html
== child-index-no-parent-01.html child-index-no-parent-01-ref.html
== class-id-attr-selector-invalidation-01.html class-id-attr-selector-invalidation-01-ref.html

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

@ -16,6 +16,9 @@ ServoElementSnapshot::ServoElementSnapshot(const Element* aElement)
, mContains(Flags(0))
, mIsTableBorderNonzero(false)
, mIsMozBrowserFrame(false)
, mClassAttributeChanged(false)
, mIdAttributeChanged(false)
, mOtherAttributeChanged(false)
{
MOZ_COUNT_CTOR(ServoElementSnapshot);
mIsHTMLElementInHTMLDocument =
@ -30,10 +33,24 @@ ServoElementSnapshot::~ServoElementSnapshot()
}
void
ServoElementSnapshot::AddAttrs(Element* aElement)
ServoElementSnapshot::AddAttrs(Element* aElement,
int32_t aNameSpaceID,
nsIAtom* aAttribute)
{
MOZ_ASSERT(aElement);
if (aNameSpaceID == kNameSpaceID_None) {
if (aAttribute == nsGkAtoms::_class) {
mClassAttributeChanged = true;
} else if (aAttribute == nsGkAtoms::id) {
mIdAttributeChanged = true;
} else {
mOtherAttributeChanged = true;
}
} else {
mOtherAttributeChanged = true;
}
if (HasAttrs()) {
return;
}

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

@ -94,8 +94,13 @@ public:
/**
* Captures the given element attributes (if not previously captured).
*
* The attribute name and namespace are used to note which kind of attribute
* has changed.
*/
void AddAttrs(Element* aElement);
void AddAttrs(Element* aElement,
int32_t aNameSpaceID,
nsIAtom* aChangedAttribute);
/**
* Captures some other pseudo-class matching state not included in
@ -175,6 +180,9 @@ private:
bool mSupportsLangAttr : 1;
bool mIsTableBorderNonzero : 1;
bool mIsMozBrowserFrame : 1;
bool mClassAttributeChanged : 1;
bool mIdAttributeChanged : 1;
bool mOtherAttributeChanged : 1;
};
} // namespace mozilla

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

@ -224,6 +224,8 @@ skip-if = toolkit == 'android'
[test_initial_computation.html]
skip-if = toolkit == 'android'
[test_initial_storage.html]
[test_invalidation_basic.html]
skip-if = !stylo
[test_keyframes_rules.html]
[test_keyframes_vendor_prefix.html]
[test_load_events_on_stylesheets.html]
@ -254,6 +256,7 @@ skip-if = android_version == '18' #debug-only failure; timed out #Android 4.3 aw
[test_redundant_font_download.html]
support-files = redundant_font_download.sjs
[test_rem_unit.html]
[test_restyle_table_wrapper.html]
[test_restyles_in_smil_animation.html]
skip-if = toolkit == 'android' # bug 1328522
[test_root_node_display.html]
@ -270,9 +273,9 @@ skip-if = toolkit == 'android' #bug 775227
[test_style_attribute_quirks.html]
[test_style_attribute_standards.html]
[test_style_struct_copy_constructors.html]
[test_stylesheet_clone_font_face.html]
[test_stylesheet_additions.html]
skip-if = !stylo
[test_stylesheet_clone_font_face.html]
[test_supports_rules.html]
[test_system_font_serialization.html]
[test_text_decoration_shorthands.html]

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

@ -42,7 +42,6 @@ to mochitest command.
* test_bug413958.html `monitorConsole` [3]
* test_parser_diagnostics_unprintables.html [550]
* Transition support:
* test_transitions_and_reframes.html `pseudo-element`: bug 1366422 [4]
* Events:
* test_animations_event_order.html [2]
* Unimplemented \@font-face descriptors:
@ -103,8 +102,6 @@ to mochitest command.
* test_selectors.html `:-moz-lwtheme` [3]
* :-moz-window-inactive bug 1348489
* test_selectors.html `:-moz-window-inactive` [2]
* Quirks mode support
* test_hover_quirk.html: hover quirks bug 1371963 [2]
* Unit should be preserved after parsing servo/servo#15346
* test_units_time.html [1]
* getComputedStyle style doesn't contain custom properties bug 1336891

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

@ -0,0 +1,45 @@
<!doctype html>
<meta charset="utf-8">
<title>
Test for bug 1368240: We only invalidate style as little as needed
</title>
<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<style>
.foo .bar {
color: red;
}
#container ~ .bar {
color: green;
}
</style>
<div id="container">
<div></div>
<div></div>
<div></div>
</div>
<div></div>
<div></div>
<script>
SimpleTest.waitForExplicitFinish();
const utils = SpecialPowers.getDOMWindowUtils(window);
// TODO(emilio): Add an API to get the style contexts we've recreated, to make
// more elaborated tests.
document.documentElement.offsetTop;
const initialRestyleGeneration = utils.restyleGeneration;
// Normally we'd restyle the whole subtree in this case, but we should go down
// the tree invalidating as little as needed (nothing in this case).
container.classList.add("foo");
document.documentElement.offsetTop;
is(utils.restyleGeneration, initialRestyleGeneration,
"Shouldn't have restyled any descendant");
container.setAttribute("id", "");
document.documentElement.offsetTop;
is(utils.restyleGeneration, initialRestyleGeneration,
"Shouldn't have restyled any sibling");
SimpleTest.finish();
</script>

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

@ -0,0 +1,33 @@
<!doctype html>
<meta charset="utf-8">
<title>
Test for bug 1371955: We don't incorrectly think that a table wrapper style
is the main table element style.
</title>
<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<style>
/* Test implicitly ::before and ::after too */
span::before, span::after {
content: "";
display: table;
}
</style>
<table id="realTable" style="margin: 10px"></table>
<span id="spanTable" style="display: table; padding: 10px;"></span>
<script>
SimpleTest.waitForExplicitFinish();
const utils = SpecialPowers.getDOMWindowUtils(window);
document.documentElement.offsetTop;
for (const element of [realTable, spanTable]) {
const previousReflowCount = utils.framesReflowed;
const previousRestyleGeneration = utils.restyleGeneration;
element.style.color = "blue";
document.documentElement.offsetTop;
isnot(previousRestyleGeneration, utils.restyleGeneration,
"We should have restyled");
is(previousReflowCount, utils.framesReflowed,
"We shouldn't have reflowed");
}
SimpleTest.finish();
</script>

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

@ -14,8 +14,8 @@
</div>
<!--
We do it this way, using `disabled`, because appending stylesheets to the
document marks a restyle as needed, so we can't accurately measure whether
we've restyled or not.
document marks a restyle for that <style> element as needed, so we can't
accurately measure whether we've restyled or not.
-->
<style id="target" disabled></style>
<script>

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

@ -26,8 +26,6 @@ public class SiteIdentity {
private String mVerifier;
private String mOrigin;
// The order of the items here relate to image levels in
// site_security_level.xml
public enum SecurityMode {
UNKNOWN,
IDENTIFIED,
@ -35,16 +33,12 @@ public class SiteIdentity {
CHROMEUI
}
// The order of the items here relate to image levels in
// site_security_level.xml
public enum MixedMode {
UNKNOWN,
BLOCKED,
LOADED
}
// The order of the items here relate to image levels in
// site_security_level.xml
public enum TrackingMode {
UNKNOWN,
TRACKING_CONTENT_BLOCKED,

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

@ -6,8 +6,6 @@
package org.mozilla.gecko.customtabs;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
@ -227,12 +225,12 @@ public class ActionBarPresenter {
if (identity == null) {
mIconView.setVisibility(View.INVISIBLE);
} else {
final SecurityModeUtil.Mode mode = SecurityModeUtil.resolve(identity);
final SecurityModeUtil.IconType type = SecurityModeUtil.resolve(identity);
mIconView.setVisibility(View.VISIBLE);
mIconView.setImageLevel(mode.ordinal());
mIconView.setImageLevel(SecurityModeUtil.getImageLevel(type));
mIdentityPopup.setSiteIdentity(identity);
if (mode == SecurityModeUtil.Mode.LOCK_SECURE) {
if (type == SecurityModeUtil.IconType.LOCK_SECURE) {
// Lock-Secure is special case. Keep its original green color.
DrawableCompat.setTintList(mIconView.getDrawable(), null);
} else {

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

@ -5,8 +5,11 @@
package org.mozilla.gecko.toolbar;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.SiteIdentity;
import org.mozilla.gecko.SiteIdentity.MixedMode;
import org.mozilla.gecko.SiteIdentity.SecurityMode;
@ -16,32 +19,91 @@ import java.util.HashMap;
import java.util.Map;
/**
* Util class which encapsulate logic of how CustomTabsActivity treats SiteIdentity.
* TODO: Bug 1347037 - This class should be reusable for other components
* Util class to help on resolving SiteIdentity to get corresponding visual result.
*/
public class SecurityModeUtil {
// defined basic mapping between SecurityMode and SecurityModeUtil.Mode
private static final Map<SecurityMode, Mode> securityModeMap;
/**
* Abstract icon type for SiteIdentity resolving algorithm. Hence no need to worry about
* Drawable level value.
*/
public enum IconType {
UNKNOWN,
DEFAULT,
SEARCH,
LOCK_SECURE,
LOCK_WARNING, // not used for now. reserve for MixedDisplayContent icon, if any.
LOCK_INSECURE,
WARNING,
TRACKING_CONTENT_BLOCKED,
TRACKING_CONTENT_LOADED
}
// Defined mapping between IconType and Drawable image-level
private static final Map<IconType, Integer> imgLevelMap = new HashMap<>();
// http://dxr.mozilla.org/mozilla-central/source/mobile/android/base/java/org/mozilla/gecko/resources/drawable/site_security_icon.xml
static {
imgLevelMap.put(IconType.UNKNOWN, 0);
imgLevelMap.put(IconType.DEFAULT, 0);
imgLevelMap.put(IconType.LOCK_SECURE, 1);
imgLevelMap.put(IconType.WARNING, 2);
imgLevelMap.put(IconType.LOCK_INSECURE, 3);
imgLevelMap.put(IconType.TRACKING_CONTENT_BLOCKED, 4);
imgLevelMap.put(IconType.TRACKING_CONTENT_LOADED, 5);
imgLevelMap.put(IconType.SEARCH, 6);
}
// defined basic mapping between SecurityMode and SecurityModeUtil.IconType
private static final Map<SecurityMode, IconType> securityModeMap;
static {
securityModeMap = new HashMap<>();
securityModeMap.put(SecurityMode.UNKNOWN, Mode.UNKNOWN);
securityModeMap.put(SecurityMode.IDENTIFIED, Mode.LOCK_SECURE);
securityModeMap.put(SecurityMode.VERIFIED, Mode.LOCK_SECURE);
securityModeMap.put(SecurityMode.CHROMEUI, Mode.UNKNOWN);
securityModeMap.put(SecurityMode.UNKNOWN, IconType.UNKNOWN);
securityModeMap.put(SecurityMode.IDENTIFIED, IconType.LOCK_SECURE);
securityModeMap.put(SecurityMode.VERIFIED, IconType.LOCK_SECURE);
securityModeMap.put(SecurityMode.CHROMEUI, IconType.UNKNOWN);
}
/**
* To resolve which mode to be used for given SiteIdentity. Its logic is similar to
* ToolbarDisplayLayout.updateSiteIdentity
* Get image level from IconType, and to use in ImageView.setImageLevel().
*
* @param type IconType which is resolved by method resolve()
* @return the image level which defined in Drawable
*/
public static int getImageLevel(@NonNull final IconType type) {
return imgLevelMap.containsKey(type)
? imgLevelMap.get(type)
: imgLevelMap.get(IconType.UNKNOWN);
}
/**
* To resolve which icon-type to be used for given SiteIdentity.
*
* @param identity An identity of a site to be resolved
* @return Corresponding mode for resolved SiteIdentity, UNKNOWN as default.
* @return Corresponding type for resolved SiteIdentity, UNKNOWN as default.
*/
public static Mode resolve(final @Nullable SiteIdentity identity) {
public static IconType resolve(@Nullable final SiteIdentity identity) {
return resolve(identity, null);
}
/**
* To resolve which icon-type to be used for given SiteIdentity.
*
* @param identity An identity of a site to be resolved
* @param url current page url
* @return Corresponding type for resolved SiteIdentity, UNKNOWN as default.
*/
public static IconType resolve(@Nullable final SiteIdentity identity,
@Nullable final String url) {
if (!TextUtils.isEmpty(url) && AboutPages.isTitlelessAboutPage(url)) {
// We always want to just show a search icon on about:home
return IconType.SEARCH;
}
if (identity == null) {
return Mode.UNKNOWN;
return IconType.UNKNOWN;
}
final SecurityMode securityMode = identity.getSecurityMode();
@ -50,35 +112,40 @@ public class SecurityModeUtil {
final TrackingMode trackingMode = identity.getTrackingMode();
final boolean securityException = identity.isSecurityException();
if (securityMode == SiteIdentity.SecurityMode.CHROMEUI) {
return Mode.UNKNOWN;
if (securityException) {
return IconType.WARNING;
} else if (trackingMode == TrackingMode.TRACKING_CONTENT_LOADED) {
return IconType.TRACKING_CONTENT_LOADED;
} else if (trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED) {
return IconType.TRACKING_CONTENT_BLOCKED;
} else if (activeMixedMode == MixedMode.LOADED) {
return IconType.LOCK_INSECURE;
} else if (displayMixedMode == MixedMode.LOADED) {
return IconType.WARNING;
}
if (securityException) {
return Mode.MIXED_MODE;
} else if (trackingMode == TrackingMode.TRACKING_CONTENT_LOADED) {
return Mode.TRACKING_CONTENT_LOADED;
} else if (trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED) {
return Mode.TRACKING_CONTENT_BLOCKED;
} else if (activeMixedMode == MixedMode.LOADED) {
return Mode.MIXED_MODE;
} else if (displayMixedMode == MixedMode.LOADED) {
return Mode.WARNING;
// Chrome-UI checking is after tracking/mixed-content, even for about: pages, as they
// can still load external sites.
if (securityMode == SiteIdentity.SecurityMode.CHROMEUI) {
return IconType.DEFAULT;
}
return securityModeMap.containsKey(securityMode)
? securityModeMap.get(securityMode)
: Mode.UNKNOWN;
: IconType.UNKNOWN;
}
// Security mode constants, which map to the icons / levels defined in:
// http://dxr.mozilla.org/mozilla-central/source/mobile/android/base/java/org/mozilla/gecko/resources/drawable/customtabs_site_security_level.xml
public enum Mode {
UNKNOWN,
LOCK_SECURE,
WARNING,
MIXED_MODE,
TRACKING_CONTENT_BLOCKED,
TRACKING_CONTENT_LOADED
/**
* For a given SiteIdentity, to check whether its tracking protection is enabled.
*
* @param identity to be checked
* @return true if tracking protection is enabled.
*/
public static boolean isTrackingProtectionEnabled(final @Nullable SiteIdentity identity) {
final TrackingMode trackingMode = (identity == null)
? TrackingMode.UNKNOWN
: identity.getTrackingMode();
return (trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED);
}
}

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

@ -15,9 +15,6 @@ import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.R;
import org.mozilla.gecko.reader.ReaderModeUtils;
import org.mozilla.gecko.SiteIdentity;
import org.mozilla.gecko.SiteIdentity.MixedMode;
import org.mozilla.gecko.SiteIdentity.SecurityMode;
import org.mozilla.gecko.SiteIdentity.TrackingMode;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.animation.PropertyAnimator;
@ -367,64 +364,17 @@ public class ToolbarDisplayLayout extends ThemedLinearLayout {
private void updateSiteIdentity(@NonNull Tab tab) {
final SiteIdentity siteIdentity = tab.getSiteIdentity();
final SecurityModeUtil.IconType type = SecurityModeUtil.resolve(siteIdentity, tab.getURL());
final int imageLevel = SecurityModeUtil.getImageLevel(type);
mSiteIdentityPopup.setSiteIdentity(siteIdentity);
final SecurityMode securityMode;
final MixedMode activeMixedMode;
final MixedMode displayMixedMode;
final TrackingMode trackingMode;
final boolean securityException;
if (siteIdentity == null) {
securityMode = SecurityMode.UNKNOWN;
activeMixedMode = MixedMode.UNKNOWN;
displayMixedMode = MixedMode.UNKNOWN;
trackingMode = TrackingMode.UNKNOWN;
securityException = false;
} else {
securityMode = siteIdentity.getSecurityMode();
activeMixedMode = siteIdentity.getMixedModeActive();
displayMixedMode = siteIdentity.getMixedModeDisplay();
trackingMode = siteIdentity.getTrackingMode();
securityException = siteIdentity.isSecurityException();
}
// This is a bit tricky, but we have one icon and three potential indicators.
// Default to the identity level
int imageLevel = securityMode.ordinal();
// about: pages should default to having no icon too (the same as SecurityMode.UNKNOWN), however
// SecurityMode.CHROMEUI has a different ordinal - hence we need to manually reset it here.
// (We then continue and process the tracking / mixed content icons as usual, even for about: pages, as they
// can still load external sites.)
if (securityMode == SecurityMode.CHROMEUI) {
imageLevel = LEVEL_DEFAULT_GLOBE; // == SecurityMode.UNKNOWN.ordinal()
}
// Check to see if any protection was overridden first
if (AboutPages.isTitlelessAboutPage(tab.getURL())) {
// We always want to just show a search icon on about:home
imageLevel = LEVEL_SEARCH_ICON;
} else if (securityException) {
imageLevel = LEVEL_WARNING_MINOR;
} else if (trackingMode == TrackingMode.TRACKING_CONTENT_LOADED) {
imageLevel = LEVEL_SHIELD_DISABLED;
} else if (trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED) {
imageLevel = LEVEL_SHIELD_ENABLED;
} else if (activeMixedMode == MixedMode.LOADED) {
imageLevel = LEVEL_LOCK_DISABLED;
} else if (displayMixedMode == MixedMode.LOADED) {
imageLevel = LEVEL_WARNING_MINOR;
}
mTrackingProtectionEnabled = SecurityModeUtil.isTrackingProtectionEnabled(siteIdentity);
if (mSecurityImageLevel != imageLevel) {
mSecurityImageLevel = imageLevel;
mSiteSecurity.setImageLevel(mSecurityImageLevel);
updatePageActions();
}
mTrackingProtectionEnabled = trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED;
}
private void updateProgress(@NonNull Tab tab) {

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

@ -461,11 +461,11 @@ if CONFIG['MOZ_ANDROID_HLS_SUPPORT']:
gvjar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x for x in [
'media/GeckoAudioInfo.java',
'media/GeckoHlsAudioRenderer.java',
'media/GeckoHlsDemuxerWrapper.java',
'media/GeckoHLSDemuxerWrapper.java',
'media/GeckoHlsPlayer.java',
'media/GeckoHlsRendererBase.java',
'media/GeckoHlsResourceWrapper.java',
'media/GeckoHlsSample.java',
'media/GeckoHLSResourceWrapper.java',
'media/GeckoHLSSample.java',
'media/GeckoHlsVideoRenderer.java',
'media/GeckoVideoInfo.java',
'media/Utils.java',

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

@ -1,27 +1,32 @@
<?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/. -->
<!-- level value is matter. It should match SecurityModeUtil.getImageLevel -->
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/site_security_unknown"
android:maxLevel="0"/>
android:maxLevel="0" />
<item
android:drawable="@drawable/lock_secure"
android:maxLevel="1"/>
android:maxLevel="1" />
<item
android:drawable="@drawable/warning_minor"
android:maxLevel="2"/>
android:maxLevel="2" />
<item
android:drawable="@drawable/lock_disabled"
android:maxLevel="3"/>
android:maxLevel="3" />
<item
android:drawable="@drawable/shield_enabled"
android:maxLevel="4"/>
android:maxLevel="4" />
<item
android:drawable="@drawable/shield_disabled"
android:maxLevel="5"/>
android:maxLevel="5" />
<item
android:drawable="@drawable/search_icon_inactive"
android:maxLevel="6" />
</level-list>

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

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:maxLevel="0" android:drawable="@drawable/site_security_unknown"/>
<item android:maxLevel="1" android:drawable="@drawable/lock_secure"/>
<item android:maxLevel="2" android:drawable="@drawable/lock_secure"/>
<item android:maxLevel="3" android:drawable="@drawable/warning_minor"/>
<item android:maxLevel="4" android:drawable="@drawable/lock_disabled"/>
<item android:maxLevel="5" android:drawable="@drawable/shield_enabled"/>
<item android:maxLevel="6" android:drawable="@drawable/shield_disabled"/>
<!-- Special icon used for about:home -->
<item android:maxLevel="999" android:drawable="@drawable/search_icon_inactive" />
</level-list>

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

@ -24,7 +24,7 @@
android:contentDescription="@string/site_security"
android:padding="3dp"
android:scaleType="fitCenter"
android:src="@drawable/customtabs_site_security_icon"
android:src="@drawable/site_security_icon"
android:visibility="invisible"/>
</FrameLayout>

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

@ -24,7 +24,7 @@
android:paddingEnd="@dimen/browser_toolbar_site_security_padding_horizontal"
android:paddingTop="@dimen/browser_toolbar_site_security_padding_vertical"
android:paddingBottom="@dimen/browser_toolbar_site_security_padding_vertical"
android:src="@drawable/site_security_level"
android:src="@drawable/site_security_icon"
android:contentDescription="@string/site_security"
android:layout_gravity="center_vertical" />

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

@ -59,7 +59,9 @@
<li><a id="supportURL">&aboutPage.support.label;</a></li>
<li><a id="privacyURL">&aboutPage.privacyPolicy.label;</a></li>
<li><a href="about:rights">&aboutPage.rights.label;</a></li>
#ifndef NIGHTLY_BUILD
<li><a id="releaseNotesURL">&aboutPage.relNotes.label;</a></li>
#endif
<li><a id="creditsURL">&aboutPage.credits.label;</a></li>
<li><a href="about:license">&aboutPage.license.label;</a></li>
<div class="bottom-border"></div>

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

@ -7,7 +7,6 @@ package org.mozilla.gecko.media;
import android.util.Log;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.MimeTypes;
@ -16,8 +15,8 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.mozglue.JNIObject;
public final class GeckoHlsDemuxerWrapper {
private static final String LOGTAG = "GeckoHlsDemuxerWrapper";
public final class GeckoHLSDemuxerWrapper {
private static final String LOGTAG = "GeckoHLSDemuxerWrapper";
private static final boolean DEBUG = true;
// NOTE : These TRACK definitions should be synced with Gecko.
@ -37,11 +36,11 @@ public final class GeckoHlsDemuxerWrapper {
private GeckoHlsPlayer mPlayer = null;
public static class HlsDemuxerCallbacks extends JNIObject
public static class Callbacks extends JNIObject
implements GeckoHlsPlayer.DemuxerCallbacks {
@WrapForJNI(calledFrom = "gecko")
HlsDemuxerCallbacks() {}
Callbacks() {}
@Override
@WrapForJNI
@ -55,7 +54,7 @@ public final class GeckoHlsDemuxerWrapper {
protected void disposeNative() {
throw new UnsupportedOperationException();
}
} // HlsDemuxerCallbacks
} // Callbacks
private static void assertTrue(boolean condition) {
if (DEBUG && !condition) {
@ -81,9 +80,9 @@ public final class GeckoHlsDemuxerWrapper {
}
@WrapForJNI(calledFrom = "gecko")
public static GeckoHlsDemuxerWrapper create(GeckoHlsPlayer player,
public static GeckoHLSDemuxerWrapper create(GeckoHlsPlayer player,
GeckoHlsPlayer.DemuxerCallbacks callback) {
return new GeckoHlsDemuxerWrapper(player, callback);
return new GeckoHLSDemuxerWrapper(player, callback);
}
@WrapForJNI
@ -143,23 +142,23 @@ public final class GeckoHlsDemuxerWrapper {
return mPlayer.seek(seekTime);
}
GeckoHlsDemuxerWrapper(GeckoHlsPlayer player,
GeckoHLSDemuxerWrapper(GeckoHlsPlayer player,
GeckoHlsPlayer.DemuxerCallbacks callback) {
if (DEBUG) Log.d(LOGTAG, "Constructing GeckoHlsDemuxerWrapper ...");
if (DEBUG) Log.d(LOGTAG, "Constructing GeckoHLSDemuxerWrapper ...");
assertTrue(callback != null);
assertTrue(player != null);
try {
this.mPlayer = player;
this.mPlayer.addDemuxerWrapperCallbackListener(callback);
} catch (Exception e) {
Log.e(LOGTAG, "Constructing GeckoHlsDemuxerWrapper ... error", e);
Log.e(LOGTAG, "Constructing GeckoHLSDemuxerWrapper ... error", e);
callback.onError(GeckoHlsPlayer.DemuxerError.UNKNOWN.code());
}
}
@WrapForJNI
private GeckoHlsSample[] getSamples(int mediaType, int number) {
ConcurrentLinkedQueue<GeckoHlsSample> samples = null;
private GeckoHLSSample[] getSamples(int mediaType, int number) {
ConcurrentLinkedQueue<GeckoHLSSample> samples = null;
// getA/VSamples will always return a non-null instance.
if (mediaType == TrackType.VIDEO.value()) {
samples = mPlayer.getVideoSamples(number);
@ -168,7 +167,7 @@ public final class GeckoHlsDemuxerWrapper {
}
assertTrue(samples.size() <= number);
return samples.toArray(new GeckoHlsSample[samples.size()]);
return samples.toArray(new GeckoHLSSample[samples.size()]);
}
@WrapForJNI

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

@ -9,16 +9,16 @@ import android.util.Log;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.mozglue.JNIObject;
public class GeckoHlsResourceWrapper {
private static final String LOGTAG = "GeckoHlsResourceWrapper";
public class GeckoHLSResourceWrapper {
private static final String LOGTAG = "GeckoHLSResourceWrapper";
private static final boolean DEBUG = false;
private GeckoHlsPlayer mPlayer = null;
private boolean mDestroy = false;
public static class HlsResourceCallbacks extends JNIObject
public static class Callbacks extends JNIObject
implements GeckoHlsPlayer.ResourceCallbacks {
@WrapForJNI(calledFrom = "gecko")
HlsResourceCallbacks() {}
Callbacks() {}
@Override
@WrapForJNI(dispatchTo = "gecko")
@ -32,11 +32,11 @@ public class GeckoHlsResourceWrapper {
protected void disposeNative() {
throw new UnsupportedOperationException();
}
} // HlsResourceCallbacks
} // Callbacks
private GeckoHlsResourceWrapper(String url,
private GeckoHLSResourceWrapper(String url,
GeckoHlsPlayer.ResourceCallbacks callback) {
if (DEBUG) Log.d(LOGTAG, "GeckoHlsResourceWrapper created with url = " + url);
if (DEBUG) Log.d(LOGTAG, "GeckoHLSResourceWrapper created with url = " + url);
assertTrue(callback != null);
mPlayer = new GeckoHlsPlayer();
@ -45,14 +45,14 @@ public class GeckoHlsResourceWrapper {
}
@WrapForJNI(calledFrom = "gecko")
public static GeckoHlsResourceWrapper create(String url,
public static GeckoHLSResourceWrapper create(String url,
GeckoHlsPlayer.ResourceCallbacks callback) {
return new GeckoHlsResourceWrapper(url, callback);
return new GeckoHLSResourceWrapper(url, callback);
}
@WrapForJNI(calledFrom = "gecko")
public GeckoHlsPlayer GetPlayer() {
// GeckoHlsResourceWrapper should always be created before others
// GeckoHLSResourceWrapper should always be created before others
assertTrue(!mDestroy);
assertTrue(mPlayer != null);
return mPlayer;

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

@ -13,12 +13,12 @@ import org.mozilla.gecko.annotation.WrapForJNI;
import java.io.IOException;
import java.nio.ByteBuffer;
public final class GeckoHlsSample {
public static final GeckoHlsSample EOS;
public final class GeckoHLSSample {
public static final GeckoHLSSample EOS;
static {
BufferInfo eosInfo = new BufferInfo();
eosInfo.set(0, 0, Long.MIN_VALUE, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
EOS = new GeckoHlsSample(null, eosInfo, null, 0);
EOS = new GeckoHLSSample(null, eosInfo, null, 0);
}
// Indicate the index of format which is used by this sample.
@ -53,12 +53,12 @@ public final class GeckoHlsSample {
return (info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0;
}
public static GeckoHlsSample create(ByteBuffer src, BufferInfo info, CryptoInfo cryptoInfo,
public static GeckoHLSSample create(ByteBuffer src, BufferInfo info, CryptoInfo cryptoInfo,
int formatIndex) {
return new GeckoHlsSample(src, info, cryptoInfo, formatIndex);
return new GeckoHLSSample(src, info, cryptoInfo, formatIndex);
}
private GeckoHlsSample(ByteBuffer buffer, BufferInfo info, CryptoInfo cryptoInfo,
private GeckoHLSSample(ByteBuffer buffer, BufferInfo info, CryptoInfo cryptoInfo,
int formatIndex) {
this.formatIndex = formatIndex;
duration = Long.MAX_VALUE;
@ -70,7 +70,7 @@ public final class GeckoHlsSample {
@Override
public String toString() {
if (isEOS()) {
return "EOS GeckoHlsSample";
return "EOS GeckoHLSSample";
}
StringBuilder str = new StringBuilder();

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

@ -7,7 +7,6 @@ package org.mozilla.gecko.media;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodec.CryptoInfo;
import android.os.Handler;
import android.util.Log;
import com.google.android.exoplayer2.C;
@ -109,7 +108,7 @@ public class GeckoHlsAudioRenderer extends GeckoHlsRendererBase {
@Override
protected void handleEndOfStream(DecoderInputBuffer bufferForRead) {
mInputStreamEnded = true;
mDemuxedInputSamples.offer(GeckoHlsSample.EOS);
mDemuxedInputSamples.offer(GeckoHLSSample.EOS);
}
@Override
@ -132,7 +131,7 @@ public class GeckoHlsAudioRenderer extends GeckoHlsRendererBase {
assertTrue(mFormats.size() >= 0);
// We add a new format in the list once format changes, so the formatIndex
// should indicate to the last(latest) format.
GeckoHlsSample sample = GeckoHlsSample.create(buffer,
GeckoHLSSample sample = GeckoHLSSample.create(buffer,
bufferInfo,
cryptoInfo,
mFormats.size() - 1);

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

@ -9,7 +9,6 @@ import android.net.Uri;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.view.Surface;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
@ -19,8 +18,6 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
@ -83,6 +80,8 @@ public class GeckoHlsPlayer implements ExoPlayer.EventListener {
private int mNumAudioTracks = 0;
private boolean mVideoInfoUpdated = false;
private boolean mAudioInfoUpdated = false;
private boolean mVideoDataArrived = false;
private boolean mAudioDataArrived = false;
HlsMediaTracksInfo(int numVideoTracks, int numAudioTracks) {
this.mNumVideoTracks = numVideoTracks;
this.mNumAudioTracks = numAudioTracks;
@ -93,11 +92,18 @@ public class GeckoHlsPlayer implements ExoPlayer.EventListener {
public int getNumOfAudioTracks() { return mNumAudioTracks; }
public void onVideoInfoUpdated() { mVideoInfoUpdated = true; }
public void onAudioInfoUpdated() { mAudioInfoUpdated = true; }
public void onDataArrived(int trackType) {
if (trackType == C.TRACK_TYPE_VIDEO) {
mVideoDataArrived = true;
} else if (trackType == C.TRACK_TYPE_AUDIO) {
mAudioDataArrived = true;
}
}
public boolean videoReady() {
return hasVideo() ? mVideoInfoUpdated : true;
return !hasVideo() || (mVideoInfoUpdated && mVideoDataArrived);
}
public boolean audioReady() {
return hasAudio() ? mAudioInfoUpdated : true;
return !hasAudio() || (mAudioInfoUpdated && mAudioDataArrived);
}
}
private HlsMediaTracksInfo mTracksInfo = null;
@ -179,7 +185,7 @@ public class GeckoHlsPlayer implements ExoPlayer.EventListener {
}
public final class ComponentEventDispatcher {
public void onDataArrived() {
public void onDataArrived(final int trackType) {
assertTrue(mMainHandler != null);
assertTrue(mComponentListener != null);
if (!mIsPlayerInitDone) {
@ -189,7 +195,7 @@ public class GeckoHlsPlayer implements ExoPlayer.EventListener {
mMainHandler.post(new Runnable() {
@Override
public void run() {
mComponentListener.onDataArrived();
mComponentListener.onDataArrived(trackType);
}
});
}
@ -231,10 +237,13 @@ public class GeckoHlsPlayer implements ExoPlayer.EventListener {
public final class ComponentListener {
// General purpose implementation
public void onDataArrived() {
public void onDataArrived(int trackType) {
assertTrue(mResourceCallbacks != null);
assertTrue(mTracksInfo != null);
Log.d(LOGTAG, "[CB][onDataArrived]");
mTracksInfo.onDataArrived(trackType);
mResourceCallbacks.onDataArrived();
checkInitDone();
}
public void onVideoInputFormatChanged(Format format) {
@ -299,7 +308,7 @@ public class GeckoHlsPlayer implements ExoPlayer.EventListener {
if (DEBUG) { Log.d(LOGTAG, "loading [" + isLoading + "]"); }
if (!isLoading) {
// To update buffered position.
mComponentEventDispatcher.onDataArrived();
mComponentEventDispatcher.onDataArrived(C.TRACK_TYPE_DEFAULT);
}
}
@ -552,14 +561,14 @@ public class GeckoHlsPlayer implements ExoPlayer.EventListener {
// =======================================================================
// API for GeckoHlsDemuxerWrapper
// =======================================================================
public ConcurrentLinkedQueue<GeckoHlsSample> getVideoSamples(int number) {
public ConcurrentLinkedQueue<GeckoHLSSample> getVideoSamples(int number) {
return mVRenderer != null ? mVRenderer.getQueuedSamples(number) :
new ConcurrentLinkedQueue<GeckoHlsSample>();
new ConcurrentLinkedQueue<GeckoHLSSample>();
}
public ConcurrentLinkedQueue<GeckoHlsSample> getAudioSamples(int number) {
public ConcurrentLinkedQueue<GeckoHLSSample> getAudioSamples(int number) {
return mARenderer != null ? mARenderer.getQueuedSamples(number) :
new ConcurrentLinkedQueue<GeckoHlsSample>();
new ConcurrentLinkedQueue<GeckoHLSSample>();
}
public long getDuration() {

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

@ -30,7 +30,7 @@ public abstract class GeckoHlsRendererBase extends BaseRenderer {
// Notify GeckoHlsPlayer about renderer's status, i.e. data has arrived.
protected GeckoHlsPlayer.ComponentEventDispatcher mPlayerEventDispatcher;
protected ConcurrentLinkedQueue<GeckoHlsSample> mDemuxedInputSamples = new ConcurrentLinkedQueue<>();
protected ConcurrentLinkedQueue<GeckoHLSSample> mDemuxedInputSamples = new ConcurrentLinkedQueue<>();
protected ByteBuffer mInputBuffer = null;
protected ArrayList<Format> mFormats = new ArrayList<Format>();
@ -64,15 +64,15 @@ public abstract class GeckoHlsRendererBase extends BaseRenderer {
return false;
}
Iterator<GeckoHlsSample> iter = mDemuxedInputSamples.iterator();
Iterator<GeckoHLSSample> iter = mDemuxedInputSamples.iterator();
long firstPTS = 0;
if (iter.hasNext()) {
GeckoHlsSample sample = iter.next();
GeckoHLSSample sample = iter.next();
firstPTS = sample.info.presentationTimeUs;
}
long lastPTS = firstPTS;
while (iter.hasNext()) {
GeckoHlsSample sample = iter.next();
GeckoHLSSample sample = iter.next();
lastPTS = sample.info.presentationTimeUs;
}
return Math.abs(lastPTS - firstPTS) > QUEUED_INPUT_SAMPLE_DURATION_THRESHOLD;
@ -87,16 +87,16 @@ public abstract class GeckoHlsRendererBase extends BaseRenderer {
public long getFirstSamplePTS() { return mFirstSampleStartTime; }
public synchronized ConcurrentLinkedQueue<GeckoHlsSample> getQueuedSamples(int number) {
ConcurrentLinkedQueue<GeckoHlsSample> samples =
new ConcurrentLinkedQueue<GeckoHlsSample>();
public synchronized ConcurrentLinkedQueue<GeckoHLSSample> getQueuedSamples(int number) {
ConcurrentLinkedQueue<GeckoHLSSample> samples =
new ConcurrentLinkedQueue<GeckoHLSSample>();
int queuedSize = mDemuxedInputSamples.size();
for (int i = 0; i < queuedSize; i++) {
if (i >= number) {
break;
}
GeckoHlsSample sample = mDemuxedInputSamples.poll();
GeckoHLSSample sample = mDemuxedInputSamples.poll();
samples.offer(sample);
}
if (samples.isEmpty()) {
@ -172,9 +172,9 @@ public abstract class GeckoHlsRendererBase extends BaseRenderer {
/*
* The place we get demuxed data from HlsMediaSource(ExoPlayer).
* The data will then be converted to GeckoHlsSample and deliver to
* The data will then be converted to GeckoHLSSample and deliver to
* GeckoHlsDemuxerWrapper for further use.
* If the return value is ture, that means a GeckoHlsSample is queued
* If the return value is ture, that means a GeckoHLSSample is queued
* successfully. We can try to feed more samples into queue.
* If the return value is false, that means we might encounter following
* situation 1) not initialized 2) input stream is ended 3) queue is full.
@ -232,7 +232,7 @@ public abstract class GeckoHlsRendererBase extends BaseRenderer {
private void maybeNotifyDataArrived() {
if (mWaitingForData && isQueuedEnoughData()) {
if (DEBUG) { Log.d(LOGTAG, "onDataArrived"); }
mPlayerEventDispatcher.onDataArrived();
mPlayerEventDispatcher.onDataArrived(getTrackType());
mWaitingForData = false;
}
}

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