зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to inbound, a=merge
MozReview-Commit-ID: 7AS5EEH6buZ
This commit is contained in:
Коммит
09a205279b
10
.cron.yml
10
.cron.yml
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче