Merge m-c to a CLOSED TREE m-i

MozReview-Commit-ID: 2JxLeQ8GYIX
This commit is contained in:
Phil Ringnalda 2016-10-27 20:36:38 -07:00
Родитель 48147456e5 445097654c
Коммит b6eec64324
149 изменённых файлов: 4591 добавлений и 1291 удалений

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

@ -65,11 +65,9 @@ browser/base/content/test/general/file_csp_block_all_mixedcontent.html
browser/base/content/test/urlbar/file_blank_but_not_blank.html
browser/base/content/newtab/**
browser/components/downloads/**
browser/components/feeds/**
browser/components/privatebrowsing/**
browser/components/sessionstore/**
browser/components/tabview/**
browser/components/translation/**
# generated files in cld2
browser/components/translation/cld2/cld-worker.js
browser/extensions/pdfjs/**

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

@ -399,14 +399,16 @@ SocialShare = {
if (!SocialUI.canSharePage(sharedURI))
return;
let browserMM = gBrowser.selectedBrowser.messageManager;
// the point of this action type is that we can use existing share
// endpoints (e.g. oexchange) that do not support additional
// socialapi functionality. One tweak is that we shoot an event
// containing the open graph data.
let _dataFn;
if (!pageData || sharedURI == gBrowser.currentURI) {
messageManager.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => {
messageManager.removeMessageListener("PageMetadata:PageDataResult", _dataFn);
browserMM.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => {
browserMM.removeMessageListener("PageMetadata:PageDataResult", _dataFn);
let pageData = msg.json;
if (graphData) {
// overwrite data retreived from page with data given to us as a param
@ -416,17 +418,17 @@ SocialShare = {
}
this.sharePage(providerOrigin, pageData, target, anchor);
});
gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetPageData", null, { target });
browserMM.sendAsyncMessage("PageMetadata:GetPageData", null, { target });
return;
}
// if this is a share of a selected item, get any microformats
if (!pageData.microformats && target) {
messageManager.addMessageListener("PageMetadata:MicroformatsResult", _dataFn = (msg) => {
messageManager.removeMessageListener("PageMetadata:MicroformatsResult", _dataFn);
browserMM.addMessageListener("PageMetadata:MicroformatsResult", _dataFn = (msg) => {
browserMM.removeMessageListener("PageMetadata:MicroformatsResult", _dataFn);
pageData.microformats = msg.data;
this.sharePage(providerOrigin, pageData, target, anchor);
});
gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetMicroformats", null, { target });
browserMM.sendAsyncMessage("PageMetadata:GetMicroformats", null, { target });
return;
}
this.currentShare = pageData;

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

@ -49,10 +49,12 @@ var gTests = [
// We need to load the content script in the first window so that we can
// catch the notifications fired globally when closing the second window.
gBrowser.selectedBrowser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
yield BrowserTestUtils.closeWindow(win);
yield expectObserverCalled("recording-window-ended");
yield expectObserverCalled("recording-device-events");
let promises = [promiseObserverCalled("recording-device-events"),
promiseObserverCalled("recording-window-ended")];
yield BrowserTestUtils.closeWindow(win);
yield Promise.all(promises);
yield expectNoObserverCalled();
yield checkNotSharing();
}

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

@ -1,4 +1,4 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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/. */
@ -227,7 +227,7 @@ FeedConverter.prototype = {
let feedReader = safeGetCharPref(getPrefActionForType(feedType), "bookmarks");
feedService.addToClientReader(result.uri.spec, title, desc, feed.type, feedReader);
return;
} catch(ex) { /* fallback to preview mode */ }
} catch (ex) { /* fallback to preview mode */ }
}
}
}

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

@ -611,7 +611,7 @@ FeedWriter.prototype = {
if (Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask")
alwaysUse = true;
}
catch(ex) { }
catch (ex) { }
this._setCheckboxCheckedState(checkbox, alwaysUse);
}
},
@ -949,7 +949,7 @@ FeedWriter.prototype = {
},
receiveMessage(msg) {
switch(msg.name) {
switch (msg.name) {
case "FeedWriter:SetApplicationLauncherMenuItem":
let menuItem = null;

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

@ -920,7 +920,7 @@ WebContentConverterRegistrarContent.prototype = {
let branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + num + ".");
try {
this._registerContentHandlerHavingBranch(branch);
} catch(ex) {
} catch (ex) {
// do nothing, the next branch might have values
}
}

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

@ -8,7 +8,7 @@ var SubscribeHandler = {
* The nsIFeedWriter object that produces the UI
*/
_feedWriter: null,
init: function SH_init() {
this._feedWriter = new BrowserFeedWriter();
},

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

@ -86,7 +86,7 @@ function checkNode(node, schema) {
var tag = schema.shift();
is(node.localName, tag, "Element should have expected tag");
while (schema.length) {
var val = schema.shift();
let val = schema.shift();
if (Array.isArray(val))
var childSchema = val;
else
@ -98,7 +98,7 @@ function checkNode(node, schema) {
};
for (var name in attrSchema) {
var [ns, nsName] = name.split(":");
var val = nsName ? node.getAttributeNS(nsTable[ns], nsName) :
let val = nsName ? node.getAttributeNS(nsTable[ns], nsName) :
node.getAttribute(name);
is(val, attrSchema[name], "Attribute " + name + " should match");
}

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

@ -28,7 +28,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=402788
else
navigator.registerContentHandler(aTxt, aUri, aTitle);
}
catch(e) {
catch (e) {
return false;
}
@ -61,10 +61,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=402788
// ftp should not work
is(testRegisterHandler(true, "foo", "ftp://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with ftp scheme should not work");
is(testRegisterHandler(false, "application/rss+xml", "ftp://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with ftp scheme should not work");
// chrome should not work
// chrome should not work
is(testRegisterHandler(true, "foo", "chrome://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with chrome scheme should not work");
is(testRegisterHandler(false, "application/rss+xml", "chrome://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with chrome scheme should not work");
// foo should not work
// foo should not work
is(testRegisterHandler(true, "foo", "foo://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with foo scheme should not work");
is(testRegisterHandler(false, "application/rss+xml", "foo://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with foo scheme should not work");

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

@ -286,7 +286,7 @@ BingRequest.prototype = {
* Initiates the request
*/
fireRequest: function() {
return Task.spawn(function *(){
return Task.spawn(function *() {
// Prepare authentication.
let token = yield BingTokenManager.getToken();
let auth = "Bearer " + token;

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

@ -166,7 +166,7 @@ this.TranslationDocument.prototype = {
item.original.push(objInMap);
str += this.generateTextForItem(objInMap);
wasLastItemPlaceholder = false;
} else {
} else if (!wasLastItemPlaceholder) {
// Otherwise, if this node doesn't contain any useful content,
// or if it is a root itself, we can replace it with a placeholder node.
// We can't simply eliminate this node from our string representation
@ -174,11 +174,9 @@ this.TranslationDocument.prototype = {
// probably merge two separate text nodes).
// It's not necessary to add more than one placeholder in sequence;
// we can optimize them away.
if (!wasLastItemPlaceholder) {
item.original.push(TranslationItem_NodePlaceholder);
str += '<br>';
wasLastItemPlaceholder = true;
}
item.original.push(TranslationItem_NodePlaceholder);
str += '<br>';
wasLastItemPlaceholder = true;
}
}
@ -277,9 +275,15 @@ TranslationItem.prototype = {
isSimpleRoot: false,
toString: function() {
let rootType = this.isRoot
? (this.isSimpleRoot ? ' (simple root)' : ' (non simple root)')
: '';
let rootType = "";
if (this.isRoot) {
if (this.isSimpleRoot) {
rootType = " (simple root)";
}
else {
rootType = " (non simple root)";
}
}
return "[object TranslationItem: <" + this.nodeRef.localName + ">"
+ rootType + "]";
},

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

@ -293,7 +293,7 @@ YandexRequest.prototype = {
* Initiates the request
*/
fireRequest: function() {
return Task.spawn(function *(){
return Task.spawn(function *() {
// Prepare URL.
let url = getUrlParam("https://translate.yandex.net/api/v1.5/tr.json/translate",
"browser.translation.yandex.translateURLOverride");

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

@ -125,7 +125,7 @@ for (let code of Object.keys(Encodings)) {
addOnPreMain(function() {
onmessage = function(aMsg){
onmessage = function(aMsg) {
let data = aMsg['data'];
let langInfo;

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

@ -103,7 +103,7 @@ add_task(function* test_handling_out_of_valid_key_error() {
*
* @param filename Name of a fixture file.
*/
function constructFixtureURL(filename){
function constructFixtureURL(filename) {
// Deduce the Mochitest server address in use from a pref that was pre-processed.
let server = Services.prefs.getCharPref("browser.translation.bing.authURL")
.replace("http://", "");

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

@ -24,7 +24,7 @@ var MetricsChecker = {
DETECT_LANG : Services.telemetry.getHistogramById("SHOULD_AUTO_DETECT_LANGUAGE"),
},
reset: function(){
reset: function() {
for (let i of Object.keys(this.HISTOGRAMS)) {
this.HISTOGRAMS[i].clear();
}
@ -65,7 +65,7 @@ var MetricsChecker = {
/**
* A recurrent loop for making assertions about collected metrics.
*/
_assertionLoop: function (prevMetrics, metrics, additions){
_assertionLoop: function (prevMetrics, metrics, additions) {
for (let metric of Object.keys(additions)) {
let addition = additions[metric];
// Allows nesting metrics. Useful for keyed histograms.
@ -117,9 +117,9 @@ var translate = Task.async(function*(text, from, closeTab = true) {
yield acceptTranslationOffer(tab);
if (closeTab) {
gBrowser.removeTab(tab);
} else {
return tab;
return null;
}
return tab;
});
function waitForMessage({messageManager}, name) {

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

@ -91,7 +91,7 @@ add_task(function* test_preference_attribution() {
*
* @param filename Name of a fixture file.
*/
function constructFixtureURL(filename){
function constructFixtureURL(filename) {
// Deduce the Mochitest server address in use from a pref that was pre-processed.
let server = Services.prefs.getCharPref("browser.translation.yandex.translateURLOverride")
.replace("http://", "");

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

@ -61,7 +61,7 @@ STUB_HOOK = $(NSINSTALL) -D '$(ABS_DIST)/$(PKG_INST_PATH)'; \
$(NULL)
endif
SEARCHPLUGINS_FILENAMES := $(shell $(PYTHON) $(srcdir)/searchplugins.py $(srcdir)/search/list.json $(AB_CD))
SEARCHPLUGINS_FILENAMES := $(shell $(call py_action,output_searchplugins_list,$(srcdir)/search/list.json $(AB_CD)))
SEARCHPLUGINS_PATH := .deps/generated_$(AB_CD)
SEARCHPLUGINS_TARGET := libs searchplugins
SEARCHPLUGINS := $(foreach plugin,$(addsuffix .xml,$(SEARCHPLUGINS_FILENAMES)),$(or $(wildcard $(call EN_US_OR_L10N_FILE,searchplugins/$(plugin))),$(warning Missing searchplugin: $(plugin))))
@ -84,7 +84,7 @@ include $(topsrcdir)/config/rules.mk
include $(topsrcdir)/toolkit/locales/l10n.mk
$(list-json): $(call mkdir_deps,$(SEARCHPLUGINS_PATH)) $(if $(IS_LANGUAGE_REPACK),FORCE)
$(shell $(PYTHON) $(srcdir)/searchjson.py $(srcdir)/search/list.json $(AB_CD) $(list-json))
$(call py_action,generate_searchjson,$(srcdir)/search/list.json $(AB_CD) $(list-json))
searchplugins:: $(list-json)
$(STAGEDIST): $(DIST)/branding

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

@ -12,6 +12,23 @@ const {createNode, TimeScale} = require("devtools/client/animationinspector/util
const { LocalizationHelper } = require("devtools/shared/l10n");
const L10N = new LocalizationHelper("devtools/locale/animationinspector.properties");
// In the createPathSegments function, an animation duration is divided by
// DURATION_RESOLUTION in order to draw the way the animation progresses.
// But depending on the timing-function, we may be not able to make the graph
// smoothly progress if this resolution is not high enough.
// So, if the difference of animation progress between 2 divisions is more than
// MIN_PROGRESS_THRESHOLD, then createPathSegments re-divides
// by DURATION_RESOLUTION.
// DURATION_RESOLUTION shoud be integer and more than 2.
const DURATION_RESOLUTION = 4;
// MIN_PROGRESS_THRESHOLD shoud be between more than 0 to 1.
const MIN_PROGRESS_THRESHOLD = 0.1;
// Show max 10 iterations for infinite animations
// to give users a clue that the animation does repeat.
const MAX_INFINITE_ANIMATIONS_ITERATIONS = 10;
// SVG namespace
const SVG_NS = "http://www.w3.org/2000/svg";
/**
* UI component responsible for displaying a single animation timeline, which
* basically looks like a rectangle that shows the delay and iterations.
@ -51,41 +68,152 @@ AnimationTimeBlock.prototype = {
// Create a container element to hold the delay and iterations.
// It is positioned according to its delay (divided by the playbackrate),
// and its width is according to its duration (divided by the playbackrate).
let {x, iterationW, delayX, delayW, negativeDelayW, endDelayX, endDelayW} =
const {x, delayX, delayW, endDelayX, endDelayW} =
TimeScale.getAnimationDimensions(animation);
// background properties for .iterations element
let backgroundIterations = TimeScale.getIterationsBackgroundData(animation);
createNode({
// Animation summary graph element.
const summaryEl = createNode({
parent: this.containerEl,
namespace: "http://www.w3.org/2000/svg",
nodeType: "svg",
attributes: {
"class": "iterations" + (state.iterationCount ? "" : " infinite"),
// Individual iterations are represented by setting the size of the
// repeating linear-gradient.
// The background-size, background-position, background-repeat represent
// iterationCount and iterationStart.
"style": `left:${x}%;
width:${iterationW}%;
background-size:${backgroundIterations.size}% 100%;
background-position:${backgroundIterations.position}% 0;
background-repeat:${backgroundIterations.repeat};`
"class": "summary",
"preserveAspectRatio": "none",
"style": `left: ${ x - (state.delay > 0 ? delayW : 0) }%`
}
});
// The animation name is displayed over the iterations.
// Note that in case of negative delay, it is pushed towards the right so
// the delay element does not overlap.
// Total displayed duration
const totalDisplayedDuration = state.playbackRate * TimeScale.getDuration();
// Calculate stroke height in viewBox to display stroke of path.
const strokeHeightForViewBox = 0.5 / this.containerEl.clientHeight;
// Set viewBox
summaryEl.setAttribute("viewBox",
`${ state.delay < 0 ? state.delay : 0 }
-${ 1 + strokeHeightForViewBox }
${ totalDisplayedDuration }
${ 1 + strokeHeightForViewBox * 2 }`);
// Get a helper function that returns the path segment of timing-function.
const segmentHelper = getSegmentHelper(state, this.win);
// Minimum segment duration is the duration of one pixel.
const minSegmentDuration =
totalDisplayedDuration / this.containerEl.clientWidth;
// Minimum progress threshold.
let minProgressThreshold = MIN_PROGRESS_THRESHOLD;
// If the easing is step function,
// minProgressThreshold should be changed by the steps.
const stepFunction = state.easing.match(/steps\((\d+)/);
if (stepFunction) {
minProgressThreshold = 1 / (parseInt(stepFunction[1], 10) + 1);
}
// Starting time of main iteration.
let mainIterationStartTime = 0;
let iterationStart = state.iterationStart;
let iterationCount = state.iterationCount ? state.iterationCount : Infinity;
// Append delay.
if (state.delay > 0) {
renderDelay(summaryEl, state, segmentHelper);
mainIterationStartTime = state.delay;
} else {
const negativeDelayCount = -state.delay / state.duration;
// Move to forward the starting point for negative delay.
iterationStart += negativeDelayCount;
// Consume iteration count by negative delay.
if (iterationCount !== Infinity) {
iterationCount -= negativeDelayCount;
}
}
// Append 1st section of iterations,
// This section is only useful in cases where iterationStart has decimals.
// e.g.
// if { iterationStart: 0.25, iterations: 3 }, firstSectionCount is 0.75.
const firstSectionCount =
iterationStart % 1 === 0
? 0 : Math.min(iterationCount, 1) - iterationStart % 1;
if (firstSectionCount) {
renderFirstIteration(summaryEl, state, mainIterationStartTime,
firstSectionCount, minSegmentDuration,
minProgressThreshold, segmentHelper);
}
if (iterationCount === Infinity) {
// If the animation repeats infinitely,
// we fill the remaining area with iteration paths.
renderInfinity(summaryEl, state, mainIterationStartTime,
firstSectionCount, totalDisplayedDuration,
minSegmentDuration, minProgressThreshold, segmentHelper);
} else {
// Otherwise, we show remaining iterations, endDelay and fill.
// Append forwards fill-mode.
if (state.fill === "both" || state.fill === "forwards") {
renderForwardsFill(summaryEl, state, mainIterationStartTime,
iterationCount, totalDisplayedDuration,
segmentHelper);
}
// Append middle section of iterations.
// e.g.
// if { iterationStart: 0.25, iterations: 3 }, middleSectionCount is 2.
const middleSectionCount =
Math.floor(iterationCount - firstSectionCount);
renderMiddleIterations(summaryEl, state, mainIterationStartTime,
firstSectionCount, middleSectionCount,
minSegmentDuration, minProgressThreshold,
segmentHelper);
// Append last section of iterations, if there is remaining iteration.
// e.g.
// if { iterationStart: 0.25, iterations: 3 }, lastSectionCount is 0.25.
const lastSectionCount =
iterationCount - middleSectionCount - firstSectionCount;
if (lastSectionCount) {
renderLastIteration(summaryEl, state, mainIterationStartTime,
firstSectionCount, middleSectionCount,
lastSectionCount, minSegmentDuration,
minProgressThreshold, segmentHelper);
}
// Append endDelay.
if (state.endDelay > 0) {
renderEndDelay(summaryEl, state,
mainIterationStartTime, iterationCount, segmentHelper);
}
}
// Append negative delay (which overlap the animation).
if (state.delay < 0) {
segmentHelper.animation.effect.timing.fill = "both";
segmentHelper.asOriginalBehavior = false;
renderNegativeDelayHiddenProgress(summaryEl, state, minSegmentDuration,
minProgressThreshold, segmentHelper);
}
// Append negative endDelay (which overlap the animation).
if (state.iterationCount && state.endDelay < 0) {
if (segmentHelper.asOriginalBehavior) {
segmentHelper.animation.effect.timing.fill = "both";
segmentHelper.asOriginalBehavior = false;
}
renderNegativeEndDelayHiddenProgress(summaryEl, state,
minSegmentDuration,
minProgressThreshold,
segmentHelper);
}
// The animation name is displayed over the animation.
createNode({
parent: createNode({
parent: this.containerEl,
attributes: {
"class": "name",
"title": this.getTooltipText(state),
// Place the name at the same position as the iterations, but make
// space for the negative delay if any.
"style": `left:${x + negativeDelayW}%;
width:${iterationW - negativeDelayW}%;`
"title": this.getTooltipText(state)
},
}),
textContent: state.name
@ -97,21 +225,25 @@ AnimationTimeBlock.prototype = {
createNode({
parent: this.containerEl,
attributes: {
"class": "delay" + (state.delay < 0 ? " negative" : ""),
"style": `left:${delayX}%;
width:${delayW}%;`
"class": "delay"
+ (state.delay < 0 ? " negative" : " positive")
+ (state.fill === "both" ||
state.fill === "backwards" ? " fill" : ""),
"style": `left:${ delayX }%; width:${ delayW }%;`
}
});
}
// endDelay
if (state.endDelay) {
if (state.iterationCount && state.endDelay) {
createNode({
parent: this.containerEl,
attributes: {
"class": "end-delay" + (state.endDelay < 0 ? " negative" : ""),
"style": `left:${endDelayX}%;
width:${endDelayW}%;`
"class": "end-delay"
+ (state.endDelay < 0 ? " negative" : " positive")
+ (state.fill === "both" ||
state.fill === "forwards" ? " fill" : ""),
"style": `left:${ endDelayX }%; width:${ endDelayW }%;`
}
});
}
@ -119,7 +251,7 @@ AnimationTimeBlock.prototype = {
getTooltipText: function (state) {
let getTime = time => L10N.getFormatStr("player.timeLabel",
L10N.numberWithDecimals(time / 1000, 2));
L10N.numberWithDecimals(time / 1000, 2));
let text = "";
@ -163,6 +295,27 @@ AnimationTimeBlock.prototype = {
text += "\n";
}
// Adding the easing.
if (state.easing) {
text += L10N.getStr("player.animationEasingLabel") + " ";
text += state.easing;
text += "\n";
}
// Adding the fill mode.
if (state.fill) {
text += L10N.getStr("player.animationFillLabel") + " ";
text += state.fill;
text += "\n";
}
// Adding the direction mode.
if (state.direction) {
text += L10N.getStr("player.animationDirectionLabel") + " ";
text += state.direction;
text += "\n";
}
// Adding the playback rate if it's different than 1.
if (state.playbackRate !== 1) {
text += L10N.getStr("player.animationRateLabel") + " ";
@ -174,10 +327,10 @@ AnimationTimeBlock.prototype = {
// needed.
if (state.propertyState) {
if (state.propertyState
.every(propState => propState.runningOnCompositor)) {
.every(propState => propState.runningOnCompositor)) {
text += L10N.getStr("player.allPropertiesOnCompositorTooltip");
} else if (state.propertyState
.some(propState => propState.runningOnCompositor)) {
.some(propState => propState.runningOnCompositor)) {
text += L10N.getStr("player.somePropertiesOnCompositorTooltip");
}
} else if (state.isRunningOnCompositor) {
@ -190,6 +343,10 @@ AnimationTimeBlock.prototype = {
onClick: function (e) {
e.stopPropagation();
this.emit("selected", this.animation);
},
get win() {
return this.containerEl.ownerDocument.defaultView;
}
};
@ -216,3 +373,346 @@ function getFormattedAnimationTitle({state}) {
return L10N.getFormatStr(`timeline.${state.type}.nameLabel`, state.name);
}
/**
* Render delay section.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Object} state - State of animation.
* @param {Object} segmentHelper - The object returned by getSegmentHelper.
*/
function renderDelay(parentEl, state, segmentHelper) {
const startSegment = segmentHelper.getSegment(0);
const endSegment = { x: state.delay, y: startSegment.y };
appendPathElement(parentEl, [startSegment, endSegment], "delay-path");
}
/**
* Render first iteration section.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Object} state - State of animation.
* @param {Number} mainIterationStartTime - Starting time of main iteration.
* @param {Number} firstSectionCount - Iteration count of first section.
* @param {Number} minSegmentDuration - Minimum segment duration.
* @param {Number} minProgressThreshold - Minimum progress threshold.
* @param {Object} segmentHelper - The object returned by getSegmentHelper.
*/
function renderFirstIteration(parentEl, state, mainIterationStartTime,
firstSectionCount, minSegmentDuration,
minProgressThreshold, segmentHelper) {
const startTime = mainIterationStartTime;
const endTime = startTime + firstSectionCount * state.duration;
const segments =
createPathSegments(startTime, endTime, minSegmentDuration,
minProgressThreshold, segmentHelper);
appendPathElement(parentEl, segments, "iteration-path");
}
/**
* Render middle iterations section.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Object} state - State of animation.
* @param {Number} mainIterationStartTime - Starting time of main iteration.
* @param {Number} firstSectionCount - Iteration count of first section.
* @param {Number} middleSectionCount - Iteration count of middle section.
* @param {Number} minSegmentDuration - Minimum segment duration.
* @param {Number} minProgressThreshold - Minimum progress threshold.
* @param {Object} segmentHelper - The object returned by getSegmentHelper.
*/
function renderMiddleIterations(parentEl, state, mainIterationStartTime,
firstSectionCount, middleSectionCount,
minSegmentDuration, minProgressThreshold,
segmentHelper) {
const offset = mainIterationStartTime + firstSectionCount * state.duration;
for (let i = 0; i < middleSectionCount; i++) {
// Get the path segments of each iteration.
const startTime = offset + i * state.duration;
const endTime = startTime + state.duration;
const segments =
createPathSegments(startTime, endTime, minSegmentDuration,
minProgressThreshold, segmentHelper);
appendPathElement(parentEl, segments, "iteration-path");
}
}
/**
* Render last iteration section.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Object} state - State of animation.
* @param {Number} mainIterationStartTime - Starting time of main iteration.
* @param {Number} firstSectionCount - Iteration count of first section.
* @param {Number} middleSectionCount - Iteration count of middle section.
* @param {Number} lastSectionCount - Iteration count of last section.
* @param {Number} minSegmentDuration - Minimum segment duration.
* @param {Number} minProgressThreshold - Minimum progress threshold.
* @param {Object} segmentHelper - The object returned by getSegmentHelper.
*/
function renderLastIteration(parentEl, state, mainIterationStartTime,
firstSectionCount, middleSectionCount,
lastSectionCount, minSegmentDuration,
minProgressThreshold, segmentHelper) {
const startTime = mainIterationStartTime +
(firstSectionCount + middleSectionCount) * state.duration;
const endTime = startTime + lastSectionCount * state.duration;
const segments =
createPathSegments(startTime, endTime, minSegmentDuration,
minProgressThreshold, segmentHelper);
appendPathElement(parentEl, segments, "iteration-path");
}
/**
* Render Infinity iterations.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Object} state - State of animation.
* @param {Number} mainIterationStartTime - Starting time of main iteration.
* @param {Number} firstSectionCount - Iteration count of first section.
* @param {Number} totalDuration - Displayed max duration.
* @param {Number} minSegmentDuration - Minimum segment duration.
* @param {Number} minProgressThreshold - Minimum progress threshold.
* @param {Object} segmentHelper - The object returned by getSegmentHelper.
*/
function renderInfinity(parentEl, state, mainIterationStartTime,
firstSectionCount, totalDuration, minSegmentDuration,
minProgressThreshold, segmentHelper) {
// Calculate the number of iterations to display,
// with a maximum of MAX_INFINITE_ANIMATIONS_ITERATIONS
let uncappedInfinityIterationCount =
(totalDuration - firstSectionCount * state.duration) / state.duration;
// If there is a small floating point error resulting in, e.g. 1.0000001
// ceil will give us 2 so round first.
uncappedInfinityIterationCount =
parseFloat(uncappedInfinityIterationCount.toPrecision(6));
const infinityIterationCount =
Math.min(MAX_INFINITE_ANIMATIONS_ITERATIONS,
Math.ceil(uncappedInfinityIterationCount));
// Append first full iteration path.
const firstStartTime =
mainIterationStartTime + firstSectionCount * state.duration;
const firstEndTime = firstStartTime + state.duration;
const firstSegments =
createPathSegments(firstStartTime, firstEndTime, minSegmentDuration,
minProgressThreshold, segmentHelper);
appendPathElement(parentEl, firstSegments, "iteration-path infinity");
// Append other iterations. We can copy first segments.
const isAlternate = state.direction.match(/alternate/);
for (let i = 1; i < infinityIterationCount; i++) {
const startTime = firstStartTime + i * state.duration;
let segments;
if (isAlternate && i % 2) {
// Copy as reverse.
segments = firstSegments.map(segment => {
return { x: firstEndTime - segment.x + startTime, y: segment.y };
});
} else {
// Copy as is.
segments = firstSegments.map(segment => {
return { x: segment.x - firstStartTime + startTime, y: segment.y };
});
}
appendPathElement(parentEl, segments, "iteration-path infinity copied");
}
}
/**
* Render endDelay section.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Object} state - State of animation.
* @param {Number} mainIterationStartTime - Starting time of main iteration.
* @param {Number} iterationCount - Whole iteration count.
* @param {Object} segmentHelper - The object returned by getSegmentHelper.
*/
function renderEndDelay(parentEl, state,
mainIterationStartTime, iterationCount, segmentHelper) {
const startTime = mainIterationStartTime + iterationCount * state.duration;
const startSegment = segmentHelper.getSegment(startTime);
const endSegment = { x: startTime + state.endDelay, y: startSegment.y };
appendPathElement(parentEl, [startSegment, endSegment], "enddelay-path");
}
/**
* Render forwards fill section.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Object} state - State of animation.
* @param {Number} mainIterationStartTime - Starting time of main iteration.
* @param {Number} iterationCount - Whole iteration count.
* @param {Number} totalDuration - Displayed max duration.
* @param {Object} segmentHelper - The object returned by getSegmentHelper.
*/
function renderForwardsFill(parentEl, state, mainIterationStartTime,
iterationCount, totalDuration, segmentHelper) {
const startTime = mainIterationStartTime + iterationCount * state.duration +
(state.endDelay > 0 ? state.endDelay : 0);
const startSegment = segmentHelper.getSegment(startTime);
const endSegment = { x: totalDuration, y: startSegment.y };
appendPathElement(parentEl, [startSegment, endSegment], "fill-forwards-path");
}
/**
* Render hidden progress of negative delay.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Object} state - State of animation.
* @param {Number} minSegmentDuration - Minimum segment duration.
* @param {Number} minProgressThreshold - Minimum progress threshold.
* @param {Object} segmentHelper - The object returned by getSegmentHelper.
*/
function renderNegativeDelayHiddenProgress(parentEl, state, minSegmentDuration,
minProgressThreshold,
segmentHelper) {
const startTime = state.delay;
const endTime = 0;
const segments =
createPathSegments(startTime, endTime, minSegmentDuration,
minProgressThreshold, segmentHelper);
appendPathElement(parentEl, segments, "delay-path negative");
}
/**
* Render hidden progress of negative endDelay.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Object} state - State of animation.
* @param {Number} minSegmentDuration - Minimum segment duration.
* @param {Number} minProgressThreshold - Minimum progress threshold.
* @param {Object} segmentHelper - The object returned by getSegmentHelper.
*/
function renderNegativeEndDelayHiddenProgress(parentEl, state,
minSegmentDuration,
minProgressThreshold,
segmentHelper) {
const endTime = state.delay + state.iterationCount * state.duration;
const startTime = endTime + state.endDelay;
const segments =
createPathSegments(startTime, endTime, minSegmentDuration,
minProgressThreshold, segmentHelper);
appendPathElement(parentEl, segments, "enddelay-path negative");
}
/**
* Get a helper function which returns the segment coord from given time.
* @param {Object} state - animation state
* @param {Object} win - window object
* @return {Object} A segmentHelper object that has the following properties:
* - animation: The script animation used to get the progress
* - endTime: The end time of the animation
* - asOriginalBehavior: The spec is that the progress of animation is changed
* if the time of setCurrentTime is during the endDelay.
* Likewise, in case the time is less than 0.
* If this flag is true, we prevent the time
* to make the same animation behavior as the original.
* - getSegment: Helper function that, given a time,
* will calculate the progress through the dummy animation.
*/
function getSegmentHelper(state, win) {
// Create a dummy Animation timing data as the
// state object we're being passed in.
const timing = Object.assign({}, state, {
iterations: state.iterationCount ? state.iterationCount : Infinity
});
// Create a dummy Animation with the given timing.
const dummyAnimation =
new win.Animation(new win.KeyframeEffect(null, null, timing), null);
// Returns segment helper object.
return {
animation: dummyAnimation,
endTime: dummyAnimation.effect.getComputedTiming().endTime,
asOriginalBehavior: true,
getSegment: function (time) {
if (this.asOriginalBehavior) {
// If the given time is less than 0, returned progress is 0.
if (time < 0) {
return { x: time, y: 0 };
}
// Avoid to apply over endTime.
this.animation.currentTime = time < this.endTime ? time : this.endTime;
} else {
this.animation.currentTime = time;
}
const progress = this.animation.effect.getComputedTiming().progress;
return { x: time, y: Math.max(progress, 0) };
}
};
}
/**
* Create the path segments from given parameters.
* @param {Number} startTime - Starting time of animation.
* @param {Number} endTime - Ending time of animation.
* @param {Number} minSegmentDuration - Minimum segment duration.
* @param {Number} minProgressThreshold - Minimum progress threshold.
* @param {Object} segmentHelper - The object of getSegmentHelper.
* @return {Array} path segments -
* [{x: {Number} time, y: {Number} progress}, ...]
*/
function createPathSegments(startTime, endTime, minSegmentDuration,
minProgressThreshold, segmentHelper) {
// If the duration is too short, early return.
if (endTime - startTime < minSegmentDuration) {
return [segmentHelper.getSegment(startTime),
segmentHelper.getSegment(endTime)];
}
// Otherwise, start creating segments.
let pathSegments = [];
// Append the segment for the startTime position.
const startTimeSegment = segmentHelper.getSegment(startTime);
pathSegments.push(startTimeSegment);
let previousSegment = startTimeSegment;
// Split the duration in equal intervals, and iterate over them.
// See the definition of DURATION_RESOLUTION for more information about this.
const interval = (endTime - startTime) / DURATION_RESOLUTION;
for (let index = 1; index <= DURATION_RESOLUTION; index++) {
// Create a segment for this interval.
const currentSegment =
segmentHelper.getSegment(startTime + index * interval);
// If the distance between the Y coordinate (the animation's progress) of
// the previous segment and the Y coordinate of the current segment is too
// large, then recurse with a smaller duration to get more details
// in the graph.
if (Math.abs(currentSegment.y - previousSegment.y) > minProgressThreshold) {
// Divide the current interval (excluding start and end bounds
// by adding/subtracting 1ms).
pathSegments = pathSegments.concat(
createPathSegments(previousSegment.x + 1, currentSegment.x - 1,
minSegmentDuration, minProgressThreshold,
segmentHelper));
}
pathSegments.push(currentSegment);
previousSegment = currentSegment;
}
return pathSegments;
}
/**
* Append path element.
* @param {Element} parentEl - Parent element of this appended path element.
* @param {Array} pathSegments - Path segments. Please see createPathSegments.
* @param {String} cls - Class name.
* @return {Element} path element.
*/
function appendPathElement(parentEl, pathSegments, cls) {
// Create path string.
let path = `M${ pathSegments[0].x },0`;
pathSegments.forEach(pathSegment => {
path += ` L${ pathSegment.x },${ pathSegment.y }`;
});
path += ` L${ pathSegments[pathSegments.length - 1].x },0 Z`;
// Append and return the path element.
return createNode({
parent: parentEl,
namespace: SVG_NS,
nodeType: "path",
attributes: {
"d": path,
"class": cls,
"vector-effect": "non-scaling-stroke",
"transform": "scale(1, -1)"
}
});
}

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

@ -12,6 +12,7 @@ support-files =
doc_script_animation.html
doc_simple_animation.html
doc_multiple_animation_types.html
doc_timing_combination_animation.html
head.js
!/devtools/client/commandline/test/helpers.js
!/devtools/client/framework/test/shared-head.js
@ -56,6 +57,7 @@ skip-if = os == "linux" && !debug # Bug 1227792
[browser_animation_timeline_scrubber_exists.js]
[browser_animation_timeline_scrubber_movable.js]
[browser_animation_timeline_scrubber_moves.js]
[browser_animation_timeline_setCurrentTime.js]
[browser_animation_timeline_shows_delay.js]
[browser_animation_timeline_shows_endDelay.js]
[browser_animation_timeline_shows_iterations.js]

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

@ -21,7 +21,7 @@ add_task(function* () {
let {containerEl, animation: {state}} = timeBlockComponents[i];
checkAnimationTooltip(containerEl, state);
checkIterationBackground(containerEl, state);
checkProgressAtStartingTime(containerEl, state);
// Get the first set of keyframes (there's only one animated property
// anyway), and the first frame element from there, we're only interested in
@ -48,27 +48,14 @@ function checkAnimationTooltip(el, {iterationStart, duration}) {
ok(title.match(regex), "The tooltip shows the expected iteration start");
}
function checkIterationBackground(el, {iterationCount, iterationStart}) {
info("Check the background-image used to display iterations is offset " +
"correctly to represent the iterationStart");
let iterationsEl = el.querySelector(".iterations");
let start = getIterationStartFromBackground(iterationsEl, iterationCount);
is(start, iterationStart % 1,
"The right background-position for iteration start");
}
function getIterationStartFromBackground(el, iterationCount) {
if (iterationCount == 1) {
let size = parseFloat(/([.\d]+)%/.exec(el.style.backgroundSize)[1]);
return 1 - size / 100;
}
let size = parseFloat(/([.\d]+)%/.exec(el.style.backgroundSize)[1]);
let position = parseFloat(/([-\d]+)%/.exec(el.style.backgroundPosition)[1]);
let iterationStartW = -position / size * (100 - size);
let rounded = Math.round(iterationStartW * 100);
return rounded / 10000;
function checkProgressAtStartingTime(el, { iterationStart }) {
info("Check the progress of starting time");
const pathEl = el.querySelector(".iteration-path");
const pathSegList = pathEl.pathSegList;
const pathSeg = pathSegList.getItem(1);
const progress = pathSeg.y;
is(progress, iterationStart % 1,
`The progress at starting point should be ${ iterationStart % 1 }`);
}
function checkKeyframeOffset(timeBlockEl, frameEl, {iterationStart}) {

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

@ -0,0 +1,88 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
requestLongerTimeout(2);
// Animation.currentTime ignores neagtive delay and positive/negative endDelay
// during fill-mode, even if they are set.
// For example, when the animation timing is
// { duration: 1000, iterations: 1, endDelay: -500, easing: linear },
// the animation progress is 0.5 at 700ms because the progress stops as 0.5 at
// 500ms in original animation. However, if you set as
// animation.currentTime = 700 manually, the progress will be 0.7.
// So we modify setCurrentTime method since
// AnimationInspector should re-produce same as original animation.
// In these tests,
// we confirm the behavior of setCurrentTime by delay and endDelay.
add_task(function* () {
yield addTab(URL_ROOT + "doc_timing_combination_animation.html");
const { panel, controller } = yield openAnimationInspector();
yield clickTimelinePlayPauseButton(panel);
const timelineComponent = panel.animationsTimelineComponent;
const timeBlockComponents = timelineComponent.timeBlocks;
// Test -5000ms.
let time = -5000;
yield controller.setCurrentTimeAll(time, true);
for (let i = 0; i < timeBlockComponents.length; i++) {
yield timeBlockComponents[i].animation.refreshState();
const state = yield timeBlockComponents[i].animation.state;
info(`Check the state at ${ time }ms with `
+ `delay:${ state.delay } and endDelay:${ state.endDelay }`);
is(state.currentTime, 0,
`The currentTime should be 0 at setCurrentTime(${ time })`);
}
// Test 10000ms.
time = 10000;
yield controller.setCurrentTimeAll(time, true);
for (let i = 0; i < timeBlockComponents.length; i++) {
yield timeBlockComponents[i].animation.refreshState();
const state = yield timeBlockComponents[i].animation.state;
info(`Check the state at ${ time }ms with `
+ `delay:${ state.delay } and endDelay:${ state.endDelay }`);
const expected = state.delay < 0 ? 0 : time;
is(state.currentTime, expected,
`The currentTime should be ${ expected } at setCurrentTime(${ time }).`
+ ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`);
}
// Test 60000ms.
time = 60000;
yield controller.setCurrentTimeAll(time, true);
for (let i = 0; i < timeBlockComponents.length; i++) {
yield timeBlockComponents[i].animation.refreshState();
const state = yield timeBlockComponents[i].animation.state;
info(`Check the state at ${ time }ms with `
+ `delay:${ state.delay } and endDelay:${ state.endDelay }`);
const expected = state.delay < 0 ? time + state.delay : time;
is(state.currentTime, expected,
`The currentTime should be ${ expected } at setCurrentTime(${ time }).`
+ ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`);
}
// Test 150000ms.
time = 150000;
yield controller.setCurrentTimeAll(time, true);
for (let i = 0; i < timeBlockComponents.length; i++) {
yield timeBlockComponents[i].animation.refreshState();
const state = yield timeBlockComponents[i].animation.state;
info(`Check the state at ${ time }ms with `
+ `delay:${ state.delay } and endDelay:${ state.endDelay }`);
const currentTime = state.delay < 0 ? time + state.delay : time;
const endTime =
state.delay + state.iterationCount * state.duration + state.endDelay;
const expected =
state.endDelay < 0 && state.fill === "both" && currentTime > endTime
? endTime : currentTime;
is(state.currentTime, expected,
`The currentTime should be ${ expected } at setCurrentTime(${ time }).`
+ ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`);
}
});

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

@ -19,14 +19,23 @@ add_task(function* () {
yield selectNodeAndWaitForAnimations(".delayed", inspector);
let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
checkDelayAndName(timelineEl, true);
let animationEl = timelineEl.querySelector(".animation");
let state = panel.animationsTimelineComponent.timeBlocks[0].animation.state;
checkPath(animationEl, state);
info("Selecting a no-delay animated node");
yield selectNodeAndWaitForAnimations(".animated", inspector);
checkDelayAndName(timelineEl, false);
animationEl = timelineEl.querySelector(".animation");
state = panel.animationsTimelineComponent.timeBlocks[0].animation.state;
checkPath(animationEl, state);
info("Selecting a negative-delay animated node");
yield selectNodeAndWaitForAnimations(".negative-delay", inspector);
checkDelayAndName(timelineEl, true);
animationEl = timelineEl.querySelector(".animation");
state = panel.animationsTimelineComponent.timeBlocks[0].animation.state;
checkPath(animationEl, state);
});
function checkDelayAndName(timelineEl, hasDelay) {
@ -37,7 +46,6 @@ function checkDelayAndName(timelineEl, hasDelay) {
" a delay element, as expected");
if (hasDelay) {
let name = timelineEl.querySelector(".name");
let targetNode = timelineEl.querySelector(".target");
// Check that the delay element does not cause the timeline to overflow.
@ -45,11 +53,44 @@ function checkDelayAndName(timelineEl, hasDelay) {
let sidebarWidth = Math.round(targetNode.getBoundingClientRect().width);
ok(delayLeft >= sidebarWidth,
"The delay element isn't displayed over the sidebar");
// Check that the delay is not displayed on top of the name.
let delayRight = Math.round(delay.getBoundingClientRect().right);
let nameLeft = Math.round(name.getBoundingClientRect().left);
ok(delayRight <= nameLeft,
"The delay element does not span over the name element");
}
}
function checkPath(animationEl, state) {
// Check existance of delay path.
const delayPathEl = animationEl.querySelector(".delay-path");
if (!state.iterationCount && state.delay < 0) {
// Infinity
ok(!delayPathEl, "The delay path for Infinity should not exist");
return;
}
if (state.delay === 0) {
ok(!delayPathEl, "The delay path for zero delay should not exist");
return;
}
ok(delayPathEl, "The delay path should exist");
// Check delay path coordinates.
const pathSegList = delayPathEl.pathSegList;
const startingPathSeg = pathSegList.getItem(0);
const endingPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2);
if (state.delay < 0) {
ok(delayPathEl.classList.contains("negative"),
"The delay path should have 'negative' class");
const startingX = state.delay;
const endingX = 0;
is(startingPathSeg.x, startingX,
`The x of starting point should be ${ startingX }`);
is(endingPathSeg.x, endingX,
`The x of ending point should be ${ endingX }`);
} else {
ok(!delayPathEl.classList.contains("negative"),
"The delay path should not have 'negative' class");
const startingX = 0;
const endingX = state.delay;
is(startingPathSeg.x, startingX,
`The x of starting point should be ${ startingX }`);
is(endingPathSeg.x, endingX,
`The x of ending point should be ${ endingX }`);
}
}

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

@ -20,8 +20,11 @@ add_task(function* () {
let selector = selectors[i];
yield selectNode(selector, inspector);
let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
let animationEl = timelineEl.querySelectorAll(".animation")[0];
let animationEl = timelineEl.querySelector(".animation");
checkEndDelayAndName(animationEl);
const state =
panel.animationsTimelineComponent.timeBlocks[0].animation.state;
checkPath(animationEl, state);
}
});
@ -42,3 +45,34 @@ function checkEndDelayAndName(animationEl) {
ok(endDelayRight >= nameLeft,
"The endDelay element does not span over the name element");
}
function checkPath(animationEl, state) {
// Check existance of enddelay path.
const endDelayPathEl = animationEl.querySelector(".enddelay-path");
ok(endDelayPathEl, "The endDelay path should exist");
// Check enddelay path coordinates.
const pathSegList = endDelayPathEl.pathSegList;
const startingPathSeg = pathSegList.getItem(0);
const endingPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2);
if (state.endDelay < 0) {
ok(endDelayPathEl.classList.contains("negative"),
"The endDelay path should have 'negative' class");
const endingX = state.delay + state.iterationCount * state.duration;
const startingX = endingX + state.endDelay;
is(startingPathSeg.x, startingX,
`The x of starting point should be ${ startingX }`);
is(endingPathSeg.x, endingX,
`The x of ending point should be ${ endingX }`);
} else {
ok(!endDelayPathEl.classList.contains("negative"),
"The endDelay path should not have 'negative' class");
const startingX =
state.delay + state.iterationCount * state.duration;
const endingX = startingX + state.endDelay;
is(startingPathSeg.x, startingX,
`The x of starting point should be ${ startingX }`);
is(endingPathSeg.x, endingX,
`The x of ending point should be ${ endingX }`);
}
}

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

@ -17,36 +17,31 @@ add_task(function* () {
yield selectNodeAndWaitForAnimations(".delayed", inspector);
info("Getting the animation element from the panel");
let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
const timelineComponent = panel.animationsTimelineComponent;
const timelineEl = timelineComponent.rootWrapperEl;
let animation = timelineEl.querySelector(".time-block");
let iterations = animation.querySelector(".iterations");
// Iterations are rendered with a repeating linear-gradient, so we need to
// calculate how many iterations are represented by looking at the background
// size.
let iterationCount = getIterationCountFromBackground(iterations);
// Get iteration count from summary graph path.
let iterationCount = getIterationCount(animation);
is(iterationCount, 10,
"The animation timeline contains the right number of iterations");
ok(!iterations.classList.contains("infinite"),
"The iteration element doesn't have the infinite class");
ok(!animation.querySelector(".infinity"),
"The summary graph does not have any elements "
+ " that have infinity class");
info("Selecting another test node with an infinite animation");
yield selectNodeAndWaitForAnimations(".animated", inspector);
info("Getting the animation element from the panel again");
animation = timelineEl.querySelector(".time-block");
iterations = animation.querySelector(".iterations");
iterationCount = getIterationCountFromBackground(iterations);
iterationCount = getIterationCount(animation);
is(iterationCount, 1,
"The animation timeline contains just one iteration");
ok(iterations.classList.contains("infinite"),
"The iteration element has the infinite class");
"The animation timeline contains one iteration");
ok(animation.querySelector(".infinity"),
"The summary graph has an element that has infinity class");
});
function getIterationCountFromBackground(el) {
let backgroundSize = parseFloat(el.style.backgroundSize.split(" ")[0]);
return Math.round(100 / backgroundSize);
function getIterationCount(timeblockEl) {
return timeblockEl.querySelectorAll(".iteration-path").length;
}

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

@ -35,6 +35,15 @@ add_task(function* () {
} else {
ok(!title.match(/Repeats: /), "The tooltip doesn't show the iterations");
}
if (controller.animationPlayers[i].state.easing) {
ok(title.match(/Easing: /), "The tooltip shows the easing");
}
if (controller.animationPlayers[i].state.fill) {
ok(title.match(/Fill: /), "The tooltip shows the fill");
}
if (controller.animationPlayers[i].state.direction) {
ok(title.match(/Direction: /), "The tooltip shows the direction");
}
ok(!title.match(/Iteration start:/),
"The tooltip doesn't show the iteration start");
});

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

@ -24,13 +24,13 @@ add_task(function* () {
info("The first animation has its rate set to 1, let's measure it");
let el = timeBlocks[0];
let duration = parseInt(el.querySelector(".iterations").style.width, 10);
let duration = getDuration(el.querySelector("path"));
let delay = parseInt(el.querySelector(".delay").style.width, 10);
info("The second animation has its rate set to 2, so should be shorter");
let el2 = timeBlocks[1];
let duration2 = parseInt(el2.querySelector(".iterations").style.width, 10);
let duration2 = getDuration(el2.querySelector("path"));
let delay2 = parseInt(el2.querySelector(".delay").style.width, 10);
// The width are calculated by the animation-inspector dynamically depending
@ -41,3 +41,41 @@ add_task(function* () {
let delayDelta = (2 * delay2) - delay;
ok(delayDelta <= 1, "The delay width is correct");
});
function getDuration(pathEl) {
const pathSegList = pathEl.pathSegList;
// Find the index of starting iterations.
let startingIterationIndex = 0;
const firstPathSeg = pathSegList.getItem(1);
for (let i = 2, n = pathSegList.numberOfItems - 2; i < n; i++) {
// Changing point of the progress acceleration is the time.
const pathSeg = pathSegList.getItem(i);
if (firstPathSeg.y != pathSeg.y) {
startingIterationIndex = i;
break;
}
}
// Find the index of ending iterations.
let endingIterationIndex = 0;
let previousPathSegment = pathSegList.getItem(startingIterationIndex);
for (let i = startingIterationIndex + 1, n = pathSegList.numberOfItems - 2;
i < n; i++) {
// Find forwards fill-mode.
const pathSeg = pathSegList.getItem(i);
if (previousPathSegment.y == pathSeg.y) {
endingIterationIndex = i;
break;
}
previousPathSegment = pathSeg;
}
if (endingIterationIndex) {
// Not forwards fill-mode
endingIterationIndex = pathSegList.numberOfItems - 2;
}
// Return the distance of starting and ending
const startingIterationPathSegment =
pathSegList.getItem(startingIterationIndex);
const endingIterationPathSegment =
pathSegList.getItem(startingIterationIndex);
return endingIterationPathSegment.x - startingIterationPathSegment.x;
}

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

@ -37,7 +37,7 @@ add_task(function* () {
is(animationEl.querySelector(".name").textContent,
animation.state.name,
"The name on the timeline is correct");
ok(animationEl.querySelector(".iterations"),
"The timeline has iterations displayed");
ok(animationEl.querySelector("svg path"),
"The timeline has svg and path element as summary graph");
}
});

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

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
div {
display: inline-block;
width: 100px;
height: 100px;
background-color: lime;
}
</style>
</head>
<body>
<script>
"use strict";
const delayList = [0, 50000, -50000];
const endDelayList = [0, 50000, -50000];
delayList.forEach(delay => {
endDelayList.forEach(endDelay => {
const el = document.createElement("div");
document.body.appendChild(el);
el.animate({ opacity: [0, 1] },
{ duration: 200000,
iterations: 1,
fill: "both",
delay: delay,
endDelay: endDelay });
});
});
</script>
</body>
</html>

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

@ -27,6 +27,7 @@ const MILLIS_TIME_FORMAT_MAX_DURATION = 4000;
* {attrName1:value1, attrName2: value2, ...}
* - parent {DOMNode} Mandatory node to append the newly created node to.
* - textContent {String} Optional text for the node.
* - namespace {String} Optional namespace
* @return {DOMNode} The newly created node.
*/
function createNode(options) {
@ -35,7 +36,10 @@ function createNode(options) {
}
let type = options.nodeType || "div";
let node = options.parent.ownerDocument.createElement(type);
let node =
options.namespace
? options.parent.ownerDocument.createElementNS(options.namespace, type)
: options.parent.ownerDocument.createElement(type);
for (let name in options.attributes || {}) {
let value = options.attributes[name];
@ -264,31 +268,6 @@ var TimeScale = {
return {x, w, iterationW, delayX, delayW, negativeDelayW,
endDelayX, endDelayW};
},
/**
* Given an animation, get the background data for .iterations element.
* This background represents iterationCount and iterationStart.
* Returns three properties.
* 1. size: x of background-size (%)
* 2. position: x of background-position (%)
* 3. repeat: background-repeat (string)
*/
getIterationsBackgroundData: function ({state}) {
let iterationCount = state.iterationCount || 1;
let iterationStartW = state.iterationStart % 1 * 100;
let background = {};
if (iterationCount == 1) {
background.size = 100 - iterationStartW;
background.position = 0;
background.repeat = "no-repeat";
} else {
background.size = 1 / iterationCount * 100;
background.position = -iterationStartW * background.size /
(100 - background.size);
background.repeat = "repeat-x";
}
return background;
}
};

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

@ -191,7 +191,6 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
unBlackBoxButton: () => this._onStopBlackBoxing(),
prettyPrintCommand: () => this.togglePrettyPrint(),
toggleBreakpointsCommand: () =>this.toggleBreakpoints(),
togglePromiseDebuggerCommand: () => this.togglePromiseDebugger(),
nextSourceCommand: () => this.selectNextItem(),
prevSourceCommand: () => this.selectPrevItem()
});
@ -614,17 +613,6 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
}
},
togglePromiseDebugger: function () {
if (Prefs.promiseDebuggerEnabled) {
let promisePane = this.DebuggerView._promisePane;
promisePane.hidden = !promisePane.hidden;
if (!this.DebuggerView._promiseDebuggerIframe) {
this.DebuggerView._initializePromiseDebugger();
}
}
},
hidePrettyPrinting: function () {
this._prettyPrintButton.style.display = "none";

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

@ -1211,7 +1211,6 @@ var Prefs = new PrefsHelper("devtools", {
workersEnabled: ["Bool", "debugger.workers"],
editorTabSize: ["Int", "editor.tabsize"],
autoBlackBox: ["Bool", "debugger.auto-black-box"],
promiseDebuggerEnabled: ["Bool", "debugger.promise"]
});
/**

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

@ -23,8 +23,6 @@ const SEARCH_VARIABLE_FLAG = "*";
const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG];
const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
const RESIZE_REFRESH_RATE = 50; // ms
const PROMISE_DEBUGGER_URL =
"chrome://devtools/content/promisedebugger/promise-debugger.xhtml";
const EventListenersView = require("./content/views/event-listeners-view");
const SourcesView = require("./content/views/sources-view");
@ -135,7 +133,6 @@ var DebuggerView = {
this.WatchExpressions.destroy();
this.EventListeners.destroy();
this.GlobalSearch.destroy();
this._destroyPromiseDebugger();
this._destroyPanes();
this.editor.destroy();
@ -155,7 +152,6 @@ var DebuggerView = {
this._workersAndSourcesPane = document.getElementById("workers-and-sources-pane");
this._instrumentsPane = document.getElementById("instruments-pane");
this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
this._promisePane = document.getElementById("promise-debugger-pane");
this.showEditor = this.showEditor.bind(this);
this.showBlackBoxMessage = this.showBlackBoxMessage.bind(this);
@ -191,7 +187,6 @@ var DebuggerView = {
this._workersAndSourcesPane = null;
this._instrumentsPane = null;
this._instrumentsPaneToggleButton = null;
this._promisePane = null;
},
/**
@ -238,41 +233,6 @@ var DebuggerView = {
});
},
/**
* Initialie the Promise Debugger instance.
*/
_initializePromiseDebugger: function () {
let iframe = this._promiseDebuggerIframe = document.createElement("iframe");
iframe.setAttribute("flex", 1);
let onLoad = (event) => {
iframe.removeEventListener("load", onLoad, true);
let doc = event.target;
let win = doc.defaultView;
win.setPanel(DebuggerController._toolbox);
};
iframe.addEventListener("load", onLoad, true);
iframe.setAttribute("src", PROMISE_DEBUGGER_URL);
this._promisePane.appendChild(iframe);
},
/**
* Destroy the Promise Debugger instance.
*/
_destroyPromiseDebugger: function () {
if (this._promiseDebuggerIframe) {
this._promiseDebuggerIframe.contentWindow.destroy();
this._promiseDebuggerIframe.parentNode.removeChild(
this._promiseDebuggerIframe);
this._promiseDebuggerIframe = null;
}
},
/**
* Initializes the Editor instance.
*

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

@ -341,11 +341,6 @@
class="devtools-toolbarbutton"
tooltiptext="&debuggerUI.sources.toggleBreakpoints;"
command="toggleBreakpointsCommand"/>
<toolbarbutton id="toggle-promise-debugger"
class="devtools-toolbarbutton"
tooltiptext="&debuggerUI.sources.togglePromiseDebugger;"
command="togglePromiseDebuggerCommand"
hidden="true"/>
</toolbar>
</tabpanel>
<tabpanel id="callstack-tabpanel">
@ -403,12 +398,6 @@
</tabpanels>
</tabbox>
</hbox>
<splitter id="editor-and-promise-splitter"
class="devtools-horizontal-splitter"/>
<vbox id="promise-debugger-pane"
flex="1"
hidden="true">
</vbox>
</vbox>
<splitter id="vertical-layout-splitter"
class="devtools-horizontal-splitter"/>

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

@ -125,17 +125,11 @@ function testHost(aPanel, aHostType, aLayoutType) {
"The workers and sources pane's parent is correct for the horizontal layout.");
is(gView._instrumentsPane.parentNode.id, "editor-and-instruments-pane",
"The instruments pane's parent is correct for the horizontal layout.");
is(gDebugger.document.getElementById("promise-debugger-pane").parentNode.id,
"debugger-content",
"The promise pane's parent is correct for the horizontal layout.");
} else {
is(gView._workersAndSourcesPane.parentNode.id, "vertical-layout-panes-container",
"The workers and sources pane's parent is correct for the vertical layout.");
is(gView._instrumentsPane.parentNode.id, "vertical-layout-panes-container",
"The instruments pane's parent is correct for the vertical layout.");
is(gDebugger.document.getElementById("promise-debugger-pane").parentNode.id,
"debugger-content",
"The promise pane's parent is correct for the horizontal layout.");
}
let widgets = gDebugger.document.getElementById("debugger-widgets").childNodes;
@ -148,7 +142,7 @@ function testHost(aPanel, aHostType, aLayoutType) {
if (aLayoutType == "horizontal") {
is(widgets.length, 5, // 1 pane, 1 content box, 2 splitters and a phantom box.
"Found the correct number of debugger widgets.");
is(content.length, 3, // 2 panes, 1 splitter.
is(content.length, 1, // 1 pane
"Found the correct number of debugger content.");
is(editorPane.length, 3, // 2 panes, 1 splitter
"Found the correct number of debugger panes.");
@ -157,7 +151,7 @@ function testHost(aPanel, aHostType, aLayoutType) {
} else {
is(widgets.length, 4, // 1 content box, 2 splitters and a phantom box.
"Found the correct number of debugger widgets.");
is(content.length, 3, // 2 panes, 1 splitter.
is(content.length, 1, // 1 pane
"Found the correct number of debugger content.");
is(editorPane.length, 2, // 1 pane, 1 splitter
"Found the correct number of debugger panes.");

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

@ -43,18 +43,17 @@ add_task(function* () {
yield testSelectEvent("styleeditor");
yield toolbox.destroy();
yield testSelectToolRace();
/**
* Assert that selecting the given toolId raises a select event
* @param {toolId} Id of the tool to test
*/
function testSelectEvent(toolId) {
return new Promise(resolve => {
toolbox.once("select", (event, id) => {
is(id, toolId, toolId + " selected");
resolve();
});
toolbox.selectTool(toolId);
});
function* testSelectEvent(toolId) {
let onSelect = toolbox.once("select");
toolbox.selectTool(toolId);
let id = yield onSelect;
is(id, toolId, toolId + " selected");
}
/**
@ -62,15 +61,41 @@ add_task(function* () {
* selected event
* @param {toolId} Id of the tool to test
*/
function testToolSelectEvent(toolId) {
return new Promise(resolve => {
toolbox.once(toolId + "-selected", () => {
let msg = toolId + " tool selected";
is(toolbox.currentToolId, toolId, msg);
resolve();
});
toolbox.selectTool(toolId);
});
function* testToolSelectEvent(toolId) {
let onSelected = toolbox.once(toolId + "-selected");
toolbox.selectTool(toolId);
yield onSelected;
is(toolbox.currentToolId, toolId, toolId + " tool selected");
}
/**
* Assert that two calls to selectTool won't race
*/
function* testSelectToolRace() {
let toolbox = yield openToolboxForTab(tab, "webconsole");
let selected = false;
let onSelect = (event, id) => {
if (selected) {
ok(false, "Got more than one 'select' event");
} else {
selected = true;
}
};
toolbox.once("select", onSelect);
let p1 = toolbox.selectTool("inspector")
let p2 = toolbox.selectTool("inspector");
// Check that both promises don't resolve too early
let checkSelectToolResolution = panel => {
ok(selected, "selectTool resolves only after 'select' event is fired");
let inspector = toolbox.getPanel("inspector");
is(panel, inspector, "selecTool resolves to the panel instance");
};
p1.then(checkSelectToolResolution);
p2.then(checkSelectToolResolution);
yield p1;
yield p2;
yield toolbox.destroy();
}
});

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

@ -1451,11 +1451,19 @@ Toolbox.prototype = {
}
if (this.currentToolId == id) {
// re-focus tool to get key events again
this.focusTool(id);
let panel = this._toolPanels.get(id);
if (panel) {
// We have a panel instance, so the tool is already fully loaded.
// Return the existing panel in order to have a consistent return value.
return promise.resolve(this._toolPanels.get(id));
// re-focus tool to get key events again
this.focusTool(id);
// Return the existing panel in order to have a consistent return value.
return promise.resolve(panel);
}
// Otherwise, if there is no panel instance, it is still loading,
// so we are racing another call to selectTool with the same id.
return this.once("select").then(() => promise.resolve(this._toolPanels.get(id)));
}
if (!this.isReady) {

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

@ -133,6 +133,8 @@ subsuite = clipboard
[browser_inspector_menu-05-attribute-items.js]
[browser_inspector_menu-06-other.js]
[browser_inspector_navigation.js]
[browser_inspector_navigate_to_errors.js]
[browser_inspector_open_on_neterror.js]
[browser_inspector_pane-toggle-01.js]
[browser_inspector_pane-toggle-02.js]
[browser_inspector_pane-toggle-03.js]

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

@ -9,14 +9,9 @@ const URL_1 = "data:text/plain;charset=UTF-8,abcde";
const URL_2 = "data:text/plain;charset=UTF-8,12345";
add_task(function* () {
let { toolbox } = yield openInspectorForURL(URL_1);
let { inspector, toolbox } = yield openInspectorForURL(URL_1);
info("Navigating to different URL.");
let navigated = toolbox.target.once("navigate");
navigateTo(toolbox, URL_2);
info("Waiting for 'navigate' event from toolbox target.");
yield navigated;
yield navigateTo(inspector, URL_2);
info("Destroying toolbox");
try {

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

@ -11,7 +11,7 @@ const TEST_URL_2 =
"data:text/html;charset=utf-8,<h1 style='color:red'>HTML test page</h1>";
add_task(function* () {
let {inspector, toolbox} = yield openInspectorForURL(TEST_URL);
let {inspector} = yield openInspectorForURL(TEST_URL);
info("Check the inspector toolbar");
let button = inspector.panelDoc.querySelector("#inspector-eyedropper-toggle");
@ -35,11 +35,7 @@ add_task(function* () {
ok(isDisabled(button), "The button is disabled in the color picker");
info("Navigate to a HTML document");
let navigated = toolbox.target.once("navigate");
let markuploaded = inspector.once("markuploaded");
navigateTo(toolbox, TEST_URL_2);
yield navigated;
yield markuploaded;
yield navigateTo(inspector, TEST_URL_2);
info("Check the inspector toolbar in HTML document");
button = inspector.panelDoc.querySelector("#inspector-eyedropper-toggle");

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

@ -0,0 +1,50 @@
/* 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";
// Test that inspector works when navigating to error pages.
const TEST_URL_1 = "data:text/html,<html><body id=\"test-doc-1\">page</body></html>";
const TEST_URL_2 = "http://127.0.0.1:36325/";
const TEST_URL_3 = "http://www.wronguri.wronguri/";
const TEST_URL_4 = "data:text/html,<html><body>test-doc-4</body></html>";
add_task(function* () {
// Open the inspector on a valid URL
let { inspector, testActor } = yield openInspectorForURL(TEST_URL_1);
info("Navigate to closed port");
yield navigateTo(inspector, TEST_URL_2);
let documentURI = yield testActor.eval("document.documentURI;");
ok(documentURI.startsWith("about:neterror"), "content is correct.");
let hasPage = yield getNodeFront("#test-doc-1", inspector);
ok(!hasPage, "Inspector actor is no longer able to reach previous page DOM node");
let hasNetErrorNode = yield getNodeFront("#errorShortDesc", inspector);
ok(hasNetErrorNode, "Inspector actor is able to reach error page DOM node");
let bundle = Services.strings.createBundle("chrome://global/locale/appstrings.properties");
let domain = TEST_URL_2.match(/^http:\/\/(.*)\/$/)[1];
let errorMsg = bundle.formatStringFromName("connectionFailure",
[domain], 1);
is(yield getDisplayedNodeTextContent("#errorShortDescText", inspector), errorMsg,
"Inpector really inspects the error page");
info("Navigate to unknown domain");
yield navigateTo(inspector, TEST_URL_3);
domain = TEST_URL_3.match(/^http:\/\/(.*)\/$/)[1];
errorMsg = bundle.formatStringFromName("dnsNotFound",
[domain], 1);
is(yield getDisplayedNodeTextContent("#errorShortDescText", inspector), errorMsg,
"Inspector really inspects the new error page");
info("Navigate to a valid url");
yield navigateTo(inspector, TEST_URL_4);
is(yield getDisplayedNodeTextContent("body", inspector), "test-doc-4",
"Inspector really inspects the valid url");
});

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

@ -14,21 +14,18 @@ const TEST_URL_1 = "http://test1.example.org/" + TEST_URL_FILE;
const TEST_URL_2 = "http://test2.example.org/" + TEST_URL_FILE;
add_task(function* () {
let { inspector, toolbox, testActor } = yield openInspectorForURL(TEST_URL_1);
let markuploaded = inspector.once("markuploaded");
let { inspector, testActor } = yield openInspectorForURL(TEST_URL_1);
yield selectNode("#i1", inspector);
info("Navigating to a different page.");
yield navigateTo(toolbox, TEST_URL_2);
info("Waiting for markup view to load after navigation.");
yield markuploaded;
yield navigateTo(inspector, TEST_URL_2);
ok(true, "New page loaded");
yield selectNode("#i1", inspector);
markuploaded = inspector.once("markuploaded");
let markuploaded = inspector.once("markuploaded");
let onUpdated = inspector.once("inspector-updated");
info("Going back in history");
yield testActor.eval("history.go(-1)");
@ -36,6 +33,9 @@ add_task(function* () {
info("Waiting for markup view to load after going back in history.");
yield markuploaded;
info("Check that the inspector updates");
yield onUpdated;
ok(true, "Old page loaded");
is((yield testActor.eval("location.href;")), TEST_URL_1, "URL is correct.");

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

@ -0,0 +1,37 @@
/* 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";
// Test that inspector works correctly when opened against a net error page
const TEST_URL_1 = "http://127.0.0.1:36325/";
const TEST_URL_2 = "data:text/html,<html><body>test-doc-2</body></html>";
add_task(function* () {
// Unfortunately, net error page are not firing load event, so that we can't
// use addTab helper and have to do that:
let tab = gBrowser.selectedTab = gBrowser.addTab("data:text/html,empty");
yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
yield ContentTask.spawn(tab.linkedBrowser, { url: TEST_URL_1 }, function* ({ url }) {
// Also, the neterror being privileged, the DOMContentLoaded only fires on
// the chromeEventHandler.
let { chromeEventHandler } = docShell; // eslint-disable-line no-undef
let onDOMContentLoaded = ContentTaskUtils.waitForEvent(chromeEventHandler,
"DOMContentLoaded", true);
content.location = url;
yield onDOMContentLoaded;
});
let { inspector, testActor } = yield openInspector();
ok(true, "Inspector loaded on the already opened net error");
let documentURI = yield testActor.eval("document.documentURI;");
ok(documentURI.startsWith("about:neterror"), "content is really a net error page.");
info("Navigate to a valid url");
yield navigateTo(inspector, TEST_URL_2);
is(yield getDisplayedNodeTextContent("body", inspector), "test-doc-2",
"Inspector really inspects the valid url");
});

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

@ -64,38 +64,32 @@ add_task(function* () {
yield selectNode(nodeToSelect, inspector);
}
let onNewRoot = inspector.once("new-root");
yield navigateToAndWaitForNewRoot(url);
info("Waiting for new root.");
yield onNewRoot;
info("Waiting for inspector to update after new-root event.");
yield inspector.once("inspector-updated");
let nodeFront = yield getNodeFront(selectedNode, inspector);
ok(nodeFront, "Got expected node front");
is(inspector.selection.nodeFront, nodeFront,
selectedNode + " is selected after navigation.");
}
function navigateToAndWaitForNewRoot(url) {
function* navigateToAndWaitForNewRoot(url) {
info("Navigating and waiting for new-root event after navigation.");
let newRoot = inspector.once("new-root");
let current = yield testActor.eval("location.href");
if (url == current) {
info("Reloading page.");
let markuploaded = inspector.once("markuploaded");
let onNewRoot = inspector.once("new-root");
let onUpdated = inspector.once("inspector-updated");
return testActor.eval("location.href")
.then(current => {
if (url == current) {
info("Reloading page.");
let activeTab = toolbox.target.activeTab;
return activeTab.reload();
}
info("Navigating to " + url);
navigateTo(toolbox, url);
return newRoot;
});
let activeTab = toolbox.target.activeTab;
yield activeTab.reload();
info("Waiting for inspector to be ready.");
yield markuploaded;
yield onNewRoot;
yield onUpdated;
} else {
yield navigateTo(inspector, url);
}
}
});

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

@ -54,10 +54,24 @@ registerCleanupFunction(function* () {
EventUtils.synthesizeMouseAtPoint(1, 1, {type: "mousemove"}, window);
});
var navigateTo = function (toolbox, url) {
let activeTab = toolbox.target.activeTab;
return activeTab.navigateTo(url);
};
var navigateTo = Task.async(function* (inspector, url) {
let markuploaded = inspector.once("markuploaded");
let onNewRoot = inspector.once("new-root");
let onUpdated = inspector.once("inspector-updated");
info("Navigating to: " + url);
let activeTab = inspector.toolbox.target.activeTab;
yield activeTab.navigateTo(url);
info("Waiting for markup view to load after navigation.");
yield markuploaded;
info("Waiting for new root.");
yield onNewRoot;
info("Waiting for inspector to update after new-root event.");
yield onUpdated;
});
/**
* Start the element picker and focus the content window.
@ -692,3 +706,26 @@ function getRuleViewRuleEditor(view, childrenIndex, nodeIndex) {
view.element.children[childrenIndex].childNodes[nodeIndex]._ruleEditor :
view.element.children[childrenIndex]._ruleEditor;
}
/**
* Get the text displayed for a given DOM Element's textContent within the
* markup view.
*
* @param {String} selector
* @param {InspectorPanel} inspector
* @return {String} The text displayed in the markup view
*/
function* getDisplayedNodeTextContent(selector, inspector) {
// We have to ensure that the textContent is displayed, for that the DOM
// Element has to be selected in the markup view and to be expanded.
yield selectNode(selector, inspector);
let container = yield getContainerForSelector(selector, inspector);
yield inspector.markup.expandNode(container.node);
yield waitForMultipleChildrenUpdates(inspector);
if (container) {
let textContainer = container.elt.querySelector("pre");
return textContainer.textContent;
}
return null;
}

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

@ -101,9 +101,6 @@ devtools.jar:
content/performance/views/recordings.js (performance/views/recordings.js)
content/memory/memory.xhtml (memory/memory.xhtml)
content/memory/initializer.js (memory/initializer.js)
content/promisedebugger/promise-controller.js (promisedebugger/promise-controller.js)
content/promisedebugger/promise-panel.js (promisedebugger/promise-panel.js)
content/promisedebugger/promise-debugger.xhtml (promisedebugger/promise-debugger.xhtml)
content/commandline/commandline.css (commandline/commandline.css)
content/commandline/commandlineoutput.xhtml (commandline/commandlineoutput.xhtml)
content/commandline/commandlinetooltip.xhtml (commandline/commandlinetooltip.xhtml)

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

@ -70,6 +70,24 @@ player.infiniteIterationCountText=∞
# %2$S will be replaced by the actual time of iteration start
player.animationIterationStartLabel=Iteration start: %1$S (%2$Ss)
# LOCALIZATION NOTE (player.animationEasingLabel):
# This string is displayed in a tooltip that appears when hovering over
# animations in the timeline. It is the label displayed before the animation
# easing value.
player.animationEasingLabel=Easing:
# LOCALIZATION NOTE (player.animationFillLabel):
# This string is displayed in a tooltip that appears when hovering over
# animations in the timeline. It is the label displayed before the animation
# fill mode value.
player.animationFillLabel=Fill:
# LOCALIZATION NOTE (player.animationDirectionLabel):
# This string is displayed in a tooltip that appears when hovering over
# animations in the timeline. It is the label displayed before the animation
# direction value.
player.animationDirectionLabel=Direction:
# LOCALIZATION NOTE (player.timeLabel):
# This string is displayed in each animation player widget, to indicate either
# how long (in seconds) the animation lasts, or what is the animation's current

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

@ -50,10 +50,6 @@
- button that toggles all breakpoints for all sources. -->
<!ENTITY debuggerUI.sources.toggleBreakpoints "Enable/disable all breakpoints">
<!-- LOCALIZATION NOTE (debuggerUI.sources.togglePromiseDebugger): This is the
- tooltip for the button that toggles the promise debugger. -->
<!ENTITY debuggerUI.sources.togglePromiseDebugger "Toggle Promise Debugger">
<!-- LOCALIZATION NOTE (debuggerUI.clearButton): This is the label for
- the button that clears the collected tracing data in the tracing tab. -->
<!ENTITY debuggerUI.clearButton "Clear">

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

@ -1,15 +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/. -->
<!-- LOCALIZATION NOTE : FILE This file contains the Promise debugger panel
strings. The Promise debugger panel is part of the debugger -->
<!-- LOCALIZATION NOTE : FILE Do not translate commandkey -->
<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
- keep it in English, or another language commonly spoken among web developers.
- You want to make that choice consistent across the developer tools.
- A good criteria is the language in which you'd find the best
- documentation on web development on the web. -->
<!ENTITY title "Promise Debugger">

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

@ -22,7 +22,6 @@ DIRS += [
'performance',
'preferences',
'projecteditor',
'promisedebugger',
'responsive.html',
'responsivedesign',
'scratchpad',

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

@ -100,7 +100,6 @@ pref("devtools.debugger.pretty-print-enabled", true);
pref("devtools.debugger.auto-pretty-print", false);
pref("devtools.debugger.auto-black-box", true);
pref("devtools.debugger.workers", false);
pref("devtools.debugger.promise", false);
#if defined(NIGHTLY_BUILD)
pref("devtools.debugger.new-debugger-frontend", true);

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

@ -1,8 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
DevToolsModules(
)

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

@ -1,102 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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/. */
/* global PromisesPanel */
"use strict";
var { utils: Cu } = Components;
const { loader, require } =
Cu.import("resource://devtools/shared/Loader.jsm", {});
const { Task } = require("devtools/shared/task");
loader.lazyRequireGetter(this, "promise");
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/shared/event-emitter");
loader.lazyRequireGetter(this, "DevToolsUtils",
"devtools/shared/DevToolsUtils");
loader.lazyRequireGetter(this, "PromisesFront",
"devtools/server/actors/promises", true);
// Global toolbox, set when startup is called.
var gToolbox;
/**
* Initialize the promise debugger controller and view upon loading the iframe.
*/
var startup = Task.async(function* (toolbox) {
gToolbox = toolbox;
yield PromisesController.initialize(toolbox);
yield PromisesPanel.initialize();
});
/**
* Destroy the promise debugger controller and view when unloading the iframe.
*/
var shutdown = Task.async(function* () {
yield PromisesController.destroy();
yield PromisesPanel.destroy();
gToolbox = null;
});
function setPanel(toolbox) {
return startup(toolbox).catch(e =>
DevToolsUtils.reportException("setPanel", e));
}
function destroy() {
return shutdown().catch(e => DevToolsUtils.reportException("destroy", e));
}
/**
* The promisedebugger controller's job is to retrieve PromisesFronts from the
* server.
*/
var PromisesController = {
initialize: Task.async(function* () {
if (this.initialized) {
return this.initialized.promise;
}
this.initialized = promise.defer();
let target = gToolbox.target;
this.promisesFront = new PromisesFront(target.client, target.form);
yield this.promisesFront.attach();
if (this.destroyed) {
console.warn("Could not fully initialize the PromisesController");
return null;
}
this.initialized.resolve();
}),
destroy: Task.async(function* () {
if (!this.initialized) {
return null;
}
if (this.destroyed) {
return this.destroyed.promise;
}
this.destroyed = promise.defer();
if (this.promisesFront) {
yield this.promisesFront.detach();
this.promisesFront.destroy();
this.promisesFront = null;
}
this.destroyed.resolve();
}),
};
EventEmitter.decorate(PromisesController);

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

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mkozilla 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/. -->
<!DOCTYPE html [
<!ENTITY % promisedebuggerDTD SYSTEM "chrome://devtools/locale/promisedebugger.dtd">
%promisedebuggerDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>&title;</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
</head>
<body class="devtools-monospace" role="application">
<script type="application/javascript;version=1.8" src="promise-controller.js"></script>
<script type="application/javascript;version=1.8" src="promise-panel.js"></script>
</body>
</html>

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

@ -1,45 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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/. */
/* global PromisesController, promise */
/* import-globals-from promise-controller.js */
"use strict";
/**
* The main promise debugger UI.
*/
var PromisesPanel = {
PANEL_INITIALIZED: "panel-initialized",
initialize: Task.async(function* () {
if (PromisesController.destroyed) {
return null;
}
if (this.initialized) {
return this.initialized.promise;
}
this.initialized = promise.defer();
this.initialized.resolve();
this.emit(this.PANEL_INITIALIZED);
}),
destroy: Task.async(function* () {
if (!this.initialized) {
return null;
}
if (this.destroyed) {
return this.destroyed.promise;
}
this.destroyed = promise.defer();
this.destroyed.resolve();
}),
};
EventEmitter.decorate(PromisesPanel);

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

@ -1,6 +0,0 @@
"use strict";
module.exports = {
// Extend from the shared list of defined globals for mochitests.
"extends": "../../../.eslintrc.mochitests.js"
};

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

@ -1,4 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";

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

@ -109,6 +109,17 @@ function tunnelToInnerBrowser(outer, inner) {
enumerable: true,
});
// The `outerWindowID` of the content is used by browser actions like view source
// and print. They send the ID down to the client to find the right content frame
// to act on.
Object.defineProperty(outer, "outerWindowID", {
get() {
return inner.outerWindowID;
},
configurable: true,
enumerable: true,
});
// The `permanentKey` property on a <xul:browser> is used to index into various maps
// held by the session store. When you swap content around with
// `_swapBrowserDocShells`, these keys are also swapped so they follow the content.
@ -274,6 +285,7 @@ function tunnelToInnerBrowser(outer, inner) {
// are on the prototype.
delete outer.frameLoader;
delete outer[FRAME_LOADER];
delete outer.outerWindowID;
// Invalidate outer's permanentKey so that SessionStore stops associating
// things that happen to the outer browser with the content inside in the
@ -406,21 +418,45 @@ MessageManagerTunnel.prototype = {
],
OUTER_TO_INNER_MESSAGE_PREFIXES: [
// Messages sent from nsContextMenu.js
"ContextMenu:",
// Messages sent from DevTools
"debug:",
// Messages sent from findbar.xml
"Findbar:",
// Messages sent from RemoteFinder.jsm
"Finder:",
// Messages sent from InlineSpellChecker.jsm
"InlineSpellChecker:",
// Messages sent from pageinfo.js
"PageInfo:",
// Messsage sent from printUtils.js
"Printing:",
// Messages sent from browser-social.js
"Social:",
"PageMetadata:",
// Messages sent from viewSourceUtils.js
"ViewSource:",
],
INNER_TO_OUTER_MESSAGE_PREFIXES: [
// Messages sent to nsContextMenu.js
"ContextMenu:",
// Messages sent to DevTools
"debug:",
// Messages sent to findbar.xml
"Findbar:",
// Messages sent to RemoteFinder.jsm
"Finder:",
// Messages sent to pageinfo.js
"PageInfo:",
// Messsage sent from printUtils.js
"Printing:",
// Messages sent to browser-social.js
"Social:",
"PageMetadata:",
// Messages sent to viewSourceUtils.js
"ViewSource:",
],
OUTER_TO_INNER_FRAME_SCRIPTS: [

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

@ -45,6 +45,12 @@
.animation {
--timeline-border-color: var(--theme-body-color);
--timeline-background-color: var(--theme-splitter-color);
/* The color of the endDelay hidden progress */
--enddelay-hidden-progress-color: var(--theme-graphs-grey);
/* The color of none fill mode */
--fill-none-color: var(--theme-highlight-gray);
/* The color of enable fill mode */
--fill-enable-color: var(--timeline-border-color);
}
.animation.cssanimation {
@ -344,53 +350,36 @@ body {
cursor: pointer;
}
/* Animation iterations */
.animation-timeline .animation .iterations {
/* Animation summary graph */
.animation-timeline .animation .summary {
position: absolute;
width: 100%;
height: 100%;
box-sizing: border-box;
/* Iterations of the animation are displayed with a repeating linear-gradient
which size is dynamically changed from JS. The gradient only draws 1px
borders between each iteration. These borders must have the same color as
the border of this element */
background-image:
linear-gradient(to left,
var(--timeline-border-color) 0,
var(--timeline-border-color) 1px,
transparent 1px,
transparent 2px);
border: 1px solid var(--timeline-border-color);
/* Border-right is already handled by the gradient */
border-width: 1px 0 1px 1px;
/* The background color is set independently */
background-color: var(--timeline-background-color);
}
.animation-timeline .animation .iterations.infinite::before,
.animation-timeline .animation .iterations.infinite::after {
content: "";
position: absolute;
top: 0;
right: 0;
width: 0;
height: 0;
border-right: 4px solid var(--theme-body-background);
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
.animation-timeline .animation .summary path {
fill: var(--timeline-background-color);
stroke: var(--timeline-border-color);
}
.animation-timeline .animation .iterations.infinite::after {
bottom: 0;
top: unset;
.animation-timeline .animation .summary .infinity.copied {
opacity: .3;
}
.animation-timeline .animation .summary path.delay-path.negative,
.animation-timeline .animation .summary path.enddelay-path.negative {
fill: none;
stroke: var(--enddelay-hidden-progress-color);
stroke-dasharray: 2, 2;
}
.animation-timeline .animation .name {
position: absolute;
color: var(--theme-selection-color);
color: var(--theme-selection-color3);
top: 0px;
left: 0px;
height: 100%;
width: 100%;
display: flex;
align-items: center;
padding: 0 2px;
@ -404,6 +393,8 @@ body {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
background-color: rgba(255, 255, 255, 0.7);
max-width: 50%;
}
.animation-timeline .fast-track .name div {
@ -423,7 +414,7 @@ body {
}
.animation-timeline .all-properties .name::after {
background-color: white;
background-color: var(--theme-content-color3);
clip-path: url(images/animation-fast-track.svg#thunderbolt);
background-repeat: no-repeat;
background-position: center;
@ -439,38 +430,41 @@ body {
.animation-timeline .animation .delay,
.animation-timeline .animation .end-delay {
position: absolute;
height: 100%;
border: 1px solid var(--timeline-border-color);
box-sizing: border-box;
border-bottom: 3px solid var(--fill-none-color);
bottom: -0.5px;
}
.animation-timeline .animation .delay {
border-width: 1px 0 1px 1px;
background-image: repeating-linear-gradient(45deg,
transparent,
transparent 1px,
var(--theme-selection-color) 1px,
var(--theme-selection-color) 4px);
background-color: var(--timeline-border-color);
.animation-timeline .animation .delay::after,
.animation-timeline .animation .end-delay::after {
content: "";
position: absolute;
top: -2px;
width: 3px;
height: 3px;
border: 2px solid var(--fill-none-color);
background-color: var(--fill-none-color);
border-radius: 50%;
}
.animation-timeline .animation .end-delay {
border-width: 1px 1px 1px 0;
background-image: repeating-linear-gradient(
-45deg,
transparent,
transparent 3px,
var(--timeline-border-color) 3px,
var(--timeline-border-color) 4px);
.animation-timeline .animation .negative.delay::after,
.animation-timeline .animation .positive.end-delay::after {
right: -3px;
}
.animation-timeline .animation .delay.negative,
.animation-timeline .animation .end-delay.negative {
/* Negative delays are displayed on top of the animation, so they need a
right border. Whereas normal delays are displayed just before the
animation, so there's already the animation's left border that serves as
a separation. */
border-width: 1px;
.animation-timeline .animation .positive.delay::after,
.animation-timeline .animation .negative.end-delay::after {
left: -3px;
}
.animation-timeline .animation .fill.delay,
.animation-timeline .animation .fill.end-delay {
border-color: var(--fill-enable-color);
}
.animation-timeline .animation .fill.delay::after,
.animation-timeline .animation .fill.end-delay::after {
border-color: var(--fill-enable-color);
background-color: var(--fill-enable-color);
}
/* Animation target node gutter, contains a preview of the dom node */

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

@ -129,10 +129,6 @@
filter: none;
}
#toggle-promise-debugger {
/* TODO Bug 1186119: Add a toggle promise debugger image */
}
#sources .black-boxed {
color: rgba(128,128,128,0.4);
}

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

@ -238,6 +238,30 @@ var AnimationPlayerActor = protocol.ActorClassWithSpec(animationPlayerSpec, {
return this.player.effect.getComputedTiming().iterationStart;
},
/**
* Get the animation easing from this player.
* @return {String}
*/
getEasing: function () {
return this.player.effect.timing.easing;
},
/**
* Get the animation fill mode from this player.
* @return {String}
*/
getFill: function () {
return this.player.effect.getComputedTiming().fill;
},
/**
* Get the animation direction from this player.
* @return {String}
*/
getDirection: function () {
return this.player.effect.getComputedTiming().direction;
},
getPropertiesCompositorStatus: function () {
let properties = this.player.effect.getProperties();
return properties.map(prop => {
@ -280,6 +304,9 @@ var AnimationPlayerActor = protocol.ActorClassWithSpec(animationPlayerSpec, {
endDelay: this.getEndDelay(),
iterationCount: this.getIterationCount(),
iterationStart: this.getIterationStart(),
fill: this.getFill(),
easing: this.getEasing(),
direction: this.getDirection(),
// animation is hitting the fast path or not. Returns false whenever the
// animation is paused as it is taken off the compositor then.
isRunningOnCompositor:
@ -397,6 +424,20 @@ var AnimationPlayerActor = protocol.ActorClassWithSpec(animationPlayerSpec, {
* Set the current time of the animation player.
*/
setCurrentTime: function (currentTime) {
// The spec is that the progress of animation is changed
// if the time of setCurrentTime is during the endDelay.
// We should prevent the time
// to make the same animation behavior as the original.
// Likewise, in case the time is less than 0.
const timing = this.player.effect.getComputedTiming();
if (timing.delay < 0) {
currentTime += timing.delay;
}
if (currentTime < 0) {
currentTime = 0;
} else if (currentTime * this.player.playbackRate > timing.endTime) {
currentTime = timing.endTime;
}
this.player.currentTime = currentTime * this.player.playbackRate;
},

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

@ -196,7 +196,9 @@ var HighlighterActor = exports.HighlighterActor = protocol.ActorClassWithSpec(hi
* Hide the box model highlighting if it was shown before
*/
hideBoxModel: function () {
this._highlighter.hide();
if (this._highlighter) {
this._highlighter.hide();
}
},
/**

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

@ -242,7 +242,12 @@ function CanvasFrameAnonymousContentHelper(highlighterEnv, nodeBuilder) {
this.anonymousContentGlobal = Cu.getGlobalForObject(
this.anonymousContentDocument);
this._insert();
// Only try to create the highlighter when the document is loaded,
// otherwise, wait for the navigate event to fire.
let doc = this.highlighterEnv.document;
if (doc.documentElement && doc.readyState != "uninitialized") {
this._insert();
}
this._onNavigate = this._onNavigate.bind(this);
this.highlighterEnv.on("navigate", this._onNavigate);

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

@ -8,7 +8,7 @@
/* global XPCNativeWrapper */
var { Ci, Cu } = require("chrome");
var { Ci, Cu, Cr } = require("chrome");
var Services = require("Services");
var { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
var promise = require("promise");
@ -2495,9 +2495,27 @@ DebuggerProgressListener.prototype = {
this._tabActor._willNavigate(window, newURI, request);
}
if (isWindow && isStop) {
// Somewhat equivalent of load event.
// (window.document.readyState == complete)
this._tabActor._navigate(window);
// Don't dispatch "navigate" event just yet when there is a redirect to
// about:neterror page.
if (request.status != Cr.NS_OK) {
// Instead, listen for DOMContentLoaded as about:neterror is loaded
// with LOAD_BACKGROUND flags and never dispatches load event.
// That may be the same reason why there is no onStateChange event
// for about:neterror loads.
let handler = getDocShellChromeEventHandler(progress);
let onLoad = evt => {
// Ignore events from iframes
if (evt.target == window.document) {
handler.removeEventListener("DOMContentLoaded", onLoad, true);
this._tabActor._navigate(window);
}
};
handler.addEventListener("DOMContentLoaded", onLoad, true);
} else {
// Somewhat equivalent of load event.
// (window.document.readyState == complete)
this._tabActor._navigate(window);
}
}
}, "DebuggerProgressListener.prototype.onStateChange")
};

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

@ -28,7 +28,10 @@
border-radius: 50%;
background: #eee;
animation: move 200s infinite, glow 100s 5;
animation: move 200s infinite , glow 100s 5;
animation-timing-function: ease-out;
animation-direction: reverse;
animation-fill-mode: both;
}
.transition {
@ -39,7 +42,7 @@
border-radius: 50%;
background: #f06;
transition: width 500s;
transition: width 500s ease-out;
}
.transition.get-round {
width: 200px;

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

@ -31,6 +31,9 @@ function* playerHasAnInitialState(walker, animations) {
ok("delay" in player.initialState, "Player's state has delay");
ok("iterationCount" in player.initialState,
"Player's state has iterationCount");
ok("fill" in player.initialState, "Player's state has fill");
ok("easing" in player.initialState, "Player's state has easing");
ok("direction" in player.initialState, "Player's state has direction");
ok("isRunningOnCompositor" in player.initialState,
"Player's state has isRunningOnCompositor");
ok("type" in player.initialState, "Player's state has type");
@ -41,56 +44,80 @@ function* playerHasAnInitialState(walker, animations) {
function* playerStateIsCorrect(walker, animations) {
info("Checking the state of the simple animation");
let state = yield getAnimationStateForNode(walker, animations,
".simple-animation", 0);
let player = yield getAnimationPlayerForNode(walker, animations,
".simple-animation", 0);
let state = yield player.getCurrentState();
is(state.name, "move", "Name is correct");
is(state.duration, 200000, "Duration is correct");
// null = infinite count
is(state.iterationCount, null, "Iteration count is correct");
is(state.fill, "none", "Fill is correct");
is(state.easing, "linear", "Easing is correct");
is(state.direction, "normal", "Direction is correct");
is(state.playState, "running", "PlayState is correct");
is(state.playbackRate, 1, "PlaybackRate is correct");
is(state.type, "cssanimation", "Type is correct");
info("Checking the state of the transition");
state = yield getAnimationStateForNode(walker, animations, ".transition", 0);
player =
yield getAnimationPlayerForNode(walker, animations, ".transition", 0);
state = yield player.getCurrentState();
is(state.name, "width", "Transition name matches transition property");
is(state.duration, 500000, "Transition duration is correct");
// transitions run only once
is(state.iterationCount, 1, "Transition iteration count is correct");
is(state.fill, "backwards", "Transition fill is correct");
is(state.easing, "linear", "Transition easing is correct");
is(state.direction, "normal", "Transition direction is correct");
is(state.playState, "running", "Transition playState is correct");
is(state.playbackRate, 1, "Transition playbackRate is correct");
is(state.type, "csstransition", "Transition type is correct");
// chech easing in keyframe
let keyframes = yield player.getFrames();
is(keyframes.length, 2, "Transition length of keyframe is correct");
is(keyframes[0].easing,
"ease-out", "Transition kerframes's easing is correct");
info("Checking the state of one of multiple animations on a node");
// Checking the 2nd player
state = yield getAnimationStateForNode(walker, animations,
".multiple-animations", 1);
player = yield getAnimationPlayerForNode(walker, animations,
".multiple-animations", 1);
state = yield player.getCurrentState();
is(state.name, "glow", "The 2nd animation's name is correct");
is(state.duration, 100000, "The 2nd animation's duration is correct");
is(state.iterationCount, 5, "The 2nd animation's iteration count is correct");
is(state.fill, "both", "The 2nd animation's fill is correct");
is(state.easing, "linear", "The 2nd animation's easing is correct");
is(state.direction, "reverse", "The 2nd animation's direction is correct");
is(state.playState, "running", "The 2nd animation's playState is correct");
is(state.playbackRate, 1, "The 2nd animation's playbackRate is correct");
// chech easing in keyframe
keyframes = yield player.getFrames();
is(keyframes.length, 2, "The 2nd animation's length of keyframe is correct");
is(keyframes[0].easing,
"ease-out", "The 2nd animation's easing of kerframes is correct");
info("Checking the state of an animation with delay");
state = yield getAnimationStateForNode(walker, animations,
".delayed-animation", 0);
player = yield getAnimationPlayerForNode(walker, animations,
".delayed-animation", 0);
state = yield player.getCurrentState();
is(state.delay, 5000, "The animation delay is correct");
info("Checking the state of an transition with delay");
state = yield getAnimationStateForNode(walker, animations,
".delayed-transition", 0);
player = yield getAnimationPlayerForNode(walker, animations,
".delayed-transition", 0);
state = yield player.getCurrentState();
is(state.delay, 3000, "The transition delay is correct");
}
function* getAnimationStateForNode(walker, animations, nodeSelector, index) {
function* getAnimationPlayerForNode(walker, animations, nodeSelector, index) {
let node = yield walker.querySelector(walker.rootNode, nodeSelector);
let players = yield animations.getAnimationPlayersForNode(node);
let player = players[index];
yield player.ready();
let state = yield player.getCurrentState();
return state;
return player;
}

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

@ -65,6 +65,9 @@ const AnimationPlayerFront = FrontClassWithSpec(animationPlayerSpec, {
endDelay: this._form.endDelay,
iterationCount: this._form.iterationCount,
iterationStart: this._form.iterationStart,
easing: this._form.easing,
fill: this._form.fill,
direction: this._form.direction,
isRunningOnCompositor: this._form.isRunningOnCompositor,
propertyState: this._form.propertyState,
documentCurrentTime: this._form.documentCurrentTime

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

@ -6,6 +6,9 @@
XPIDL_SOURCES += [
'nsIBFCacheEntry.idl',
'nsIGroupedSHistory.idl',
'nsIPartialSHistory.idl',
'nsIPartialSHistoryListener.idl',
'nsISHContainer.idl',
'nsISHEntry.idl',
'nsISHistory.idl',

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

@ -0,0 +1,47 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
#include "nsISupports.idl"
interface nsIFrameLoader;
interface nsIPartialSHistory;
/**
* nsIGroupedSHistory represent a combined session history across multiple
* root docshells (usually browser tabs). The participating nsISHistory can
* either be in chrome process or in content process, but nsIGroupedSHistory
* itself lives in chrome process. The communication is proxyed through
* nsIPartialSHistory.
*/
[scriptable, builtinclass, uuid(813e498d-73a8-449a-be09-6187e62c5352)]
interface nsIGroupedSHistory : nsISupports
{
// The total number of entries of all its partial session histories.
[infallible] readonly attribute unsigned long count;
/**
* Remove all partial histories after currently active one (if any) and then
* append the given partial session history to the end of the list.
*/
void appendPartialSessionHistory(in nsIPartialSHistory aPartialHistory);
/**
* Notify the grouped session history that the active partial session history
* has been modified. All partial session histories after the active one
* will be removed and destroy.
*/
void onPartialSessionHistoryChange(in nsIPartialSHistory aPartialHistory);
/**
* Find the proper partial session history and navigate to the entry
* corresponding to the given global index. Note it doesn't swap frameloaders,
* but rather return the target loader for the caller to swap.
*
* @param aGlobalIndex The global index to navigate to.
* @param aTargetLoaderToSwap The owner frameloader of the to-be-navigate
* partial session history.
*/
void gotoIndex(in unsigned long aGlobalIndex, out nsIFrameLoader aTargetLoaderToSwap);
};

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

@ -0,0 +1,62 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
#include "nsISupports.idl"
interface nsIFrameLoader;
/**
* nsIPartialSHistory represents a part of nsIGroupedSHistory. It associates to
* a "partial" nsISHistory in either local or remote process.
*/
[scriptable, builtinclass, uuid(5cd75e28-838c-4a0a-972e-6005f736ef7a)]
interface nsIPartialSHistory : nsISupports
{
// The number of entries of its corresponding nsISHistory.
[infallible] readonly attribute unsigned long count;
// If it's part of a grouped session history, globalIndexOffset denotes the
// number of entries ahead.
[infallible] readonly attribute unsigned long globalIndexOffset;
// The frameloader which owns this partial session history.
readonly attribute nsIFrameLoader ownerFrameLoader;
/**
* Notify that it's been added to a grouped session history. It also implies
* it's becoming the active partial history of the group.
*
* @param aOffset The number of entries in preceding partial
* session histories.
*/
void onAttachGroupedSessionHistory(in unsigned long aOffset);
/**
* Notify that one or more entries in its associated nsISHistory object
* have been changed (i.e. add / remove / replace). It's mainly used for
* cross-process case, since in the in-process case we can just register an
* nsISHistoryListener instead.
*
* @param aCount The number of entries in the associated session history.
* It can be the same as the old value if entries were replaced.
*/
void onSessionHistoryChange(in unsigned long aCount);
/**
* Notify that the partial session history has been swapped in as the active
* session history. Only an active session history can possibly add / remove /
* replace its history entries.
*
* @param aGlobalLength The up-to-date global length.
* @param aTargetLocalIndex The local index to navigate to.
*/
void onActive(in unsigned long aGlobalLength, in unsigned long aTargetLocalIndex);
/**
* Notify that the partial session history has been swapped out and is no
* longer active.
*/
void onDeactive();
};

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

@ -0,0 +1,24 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/.
*/
#include "nsISupports.idl"
/**
* Listener to handle cross partial nsISHistory navigation requests.
*/
[scriptable, uuid(be0cd2b6-6f03-4366-9fe2-184c914ff3df)]
interface nsIPartialSHistoryListener : nsISupports
{
/**
* Called when the navigation target belongs to another nsISHistory within
* the same nsIGroupedSHistory, and it needs to initiate cross nsISHistory
* navigation.
*
* @param aIndex The index of complete history to navigate to.
*/
void onRequestCrossBrowserNavigation(in unsigned long aIndex);
};

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

@ -8,6 +8,8 @@
interface nsISHEntry;
interface nsISHistoryListener;
interface nsISimpleEnumerator;
interface nsIPartialSHistoryListener;
/**
* An interface to the primary properties of the Session History
* component. In an embedded browser environment, the nsIWebBrowser
@ -30,6 +32,20 @@ interface nsISimpleEnumerator;
[scriptable, uuid(7b807041-e60a-4384-935f-af3061d8b815)]
interface nsISHistory: nsISupports
{
/**
* An attribute denoting whether the nsISHistory is associated to a grouped
* session history.
*
* The abstraction of grouped session history is implemented at
* nsIWebNavigation level, so those canGoBack / canGoForward / gotoIndex
* functions work transparently;
*
* On the other hand, nsISHistory works on partial session history directly.
* Unless otherwise specified, count / index attributes and parameters all
* indicate local count / index, so we won't mess up docshell.
*/
readonly attribute bool isPartial;
/**
* A readonly property of the interface that returns
* the number of toplevel documents currently available
@ -38,7 +54,20 @@ interface nsISHistory: nsISupports
readonly attribute long count;
/**
* A readonly property of the interface that returns
* If isPartial, globalCount denotes the total number of entries in the
* grouped session history; Otherwise it has the same value as count.
*/
readonly attribute long globalCount;
/**
* A readonly property which represents the difference between global indices
* of grouped session history and local indices of this particular session
* history object.
*/
readonly attribute long globalIndexOffset;
/**
* A readonly property of the interface that returns
* the index of the current document in session history.
*/
readonly attribute long index;
@ -124,7 +153,13 @@ interface nsISHistory: nsISupports
void removeSHistoryListener(in nsISHistoryListener aListener);
/**
* Called to obtain a enumerator for all the documents stored in
* Set the listener to handle cross nsISHistory navigation when it works
* in "partial" mode.
*/
void setPartialSHistoryListener(in nsIPartialSHistoryListener aListener);
/**
* Called to obtain a enumerator for all the documents stored in
* session history. The enumerator object thus returned by this method
* can be traversed using nsISimpleEnumerator.
*
@ -159,4 +194,28 @@ interface nsISHistory: nsISupports
* index for the given history entry.
*/
long getIndexOfEntry(in nsISHEntry aEntry);
/**
* Called when this nsISHistory has became the active history of a grouped
* session history.
*
* @param globalLength The up to date number of entries in the grouped
* session history.
* @param targetIndex The local index to navigate to.
*/
void onPartialSessionHistoryActive(in long globalLength, in long targetIndex);
/**
* Called when this nsISHistory has became inactive history of a grouped
* session history.
*/
void onPartialSessionHistoryDeactive();
/**
* Called when it's attached to a nsIGroupedSHistory instance.
*
* @param offset The number of entries in the grouped session
* history before this session history object.
*/
void onAttachGroupedSessionHistory(in long offset);
};

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

@ -98,4 +98,11 @@ interface nsISHistoryListener : nsISupports
* replaced
*/
void OnHistoryReplaceEntry(in long aIndex);
/**
* Called when nsISHistory::count has been updated. Unlike OnHistoryNewEntry
* and OnHistoryPurge which happen before the modifications are actually done
* and maybe cancellable, this function is called after these modifications.
*/
void OnLengthChange(in long aCount);
};

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

@ -226,6 +226,9 @@ nsSHistory::nsSHistory()
: mIndex(-1)
, mLength(0)
, mRequestedIndex(-1)
, mIsPartial(false)
, mGlobalIndexOffset(0)
, mEntriesInFollowingPartialHistories(0)
, mRootDocShell(nullptr)
{
// Add this new SHistory object to the list
@ -419,6 +422,11 @@ nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist)
// what the length was before, it should always be set back to the current and
// lop off the forward.
mLength = (++mIndex + 1);
NOTIFY_LISTENERS(OnLengthChange, (mLength));
// Much like how mLength works above, when changing our entries, all following
// partial histories should be purged, so we just reset the number to zero.
mEntriesInFollowingPartialHistories = 0;
// If this is the very first transaction, initialize the list
if (!mListRoot) {
@ -434,6 +442,14 @@ nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist)
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::GetIsPartial(bool* aResult)
{
NS_ENSURE_ARG_POINTER(aResult);
*aResult = mIsPartial;
return NS_OK;
}
/* Get size of the history list */
NS_IMETHODIMP
nsSHistory::GetCount(int32_t* aResult)
@ -443,6 +459,58 @@ nsSHistory::GetCount(int32_t* aResult)
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::GetGlobalCount(int32_t* aResult)
{
NS_ENSURE_ARG_POINTER(aResult);
*aResult = mGlobalIndexOffset + mLength + mEntriesInFollowingPartialHistories;
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::GetGlobalIndexOffset(int32_t* aResult)
{
NS_ENSURE_ARG_POINTER(aResult);
*aResult = mGlobalIndexOffset;
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::OnPartialSessionHistoryActive(int32_t aGlobalLength, int32_t aTargetIndex)
{
NS_ENSURE_TRUE(mIsPartial, NS_ERROR_UNEXPECTED);
int32_t extraLength = aGlobalLength - mLength - mGlobalIndexOffset;
NS_ENSURE_TRUE(extraLength >= 0, NS_ERROR_UNEXPECTED);
if (extraLength != mEntriesInFollowingPartialHistories) {
mEntriesInFollowingPartialHistories = extraLength;
}
if (mIndex == aTargetIndex) {
// TODO When we finish OnPartialSessionHistoryDeactive, we'll need to active
// the suspended document here.
// Fire location change to update canGoBack / canGoForward.
NS_DispatchToCurrentThread(NewRunnableMethod(static_cast<nsDocShell*>(mRootDocShell),
&nsDocShell::FireDummyOnLocationChange));
return NS_OK;
}
return LoadEntry(aTargetIndex, nsIDocShellLoadInfo::loadHistory,
HIST_CMD_GOTOINDEX);
}
NS_IMETHODIMP
nsSHistory::OnPartialSessionHistoryDeactive()
{
NS_ENSURE_TRUE(mIsPartial, NS_ERROR_UNEXPECTED);
// TODO We need to suspend current document first. Much like what happens when
// loading a new page. Move the ownership of the document to nsISHEntry or so.
return NS_OK;
}
/* Get index of the history list */
NS_IMETHODIMP
nsSHistory::GetIndex(int32_t* aResult)
@ -701,6 +769,10 @@ nsSHistory::PurgeHistory(int32_t aEntries)
}
mLength -= cnt;
mIndex -= cnt;
NOTIFY_LISTENERS(OnLengthChange, (mLength));
// All following partial histories will be deleted in this case.
mEntriesInFollowingPartialHistories = 0;
// Now if we were not at the end of the history, mIndex could have
// become far too negative. If so, just set it to -1.
@ -742,6 +814,13 @@ nsSHistory::RemoveSHistoryListener(nsISHistoryListener* aListener)
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::SetPartialSHistoryListener(nsIPartialSHistoryListener* aListener)
{
mPartialHistoryListener = do_GetWeakReference(aListener);
return NS_OK;
}
/* Replace an entry in the History list at a particular index.
* Do not update index or count.
*/
@ -808,14 +887,20 @@ NS_IMETHODIMP
nsSHistory::GetCanGoBack(bool* aCanGoBack)
{
NS_ENSURE_ARG_POINTER(aCanGoBack);
*aCanGoBack = false;
if (mGlobalIndexOffset) {
*aCanGoBack = true;
return NS_OK;
}
int32_t index = -1;
NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE);
if (index > 0) {
*aCanGoBack = true;
return NS_OK;
}
*aCanGoBack = false;
return NS_OK;
}
@ -823,18 +908,22 @@ NS_IMETHODIMP
nsSHistory::GetCanGoForward(bool* aCanGoForward)
{
NS_ENSURE_ARG_POINTER(aCanGoForward);
*aCanGoForward = false;
if (mEntriesInFollowingPartialHistories) {
*aCanGoForward = true;
return NS_OK;
}
int32_t index = -1;
int32_t count = -1;
NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(GetCount(&count), NS_ERROR_FAILURE);
if (index >= 0 && index < (count - 1)) {
*aCanGoForward = true;
return NS_OK;
}
*aCanGoForward = false;
return NS_OK;
}
@ -1358,6 +1447,8 @@ nsSHistory::RemoveDuplicate(int32_t aIndex, bool aKeepNext)
mRequestedIndex = mRequestedIndex - 1;
}
--mLength;
mEntriesInFollowingPartialHistories = 0;
NOTIFY_LISTENERS(OnLengthChange, (mLength));
return true;
}
return false;
@ -1513,9 +1604,11 @@ nsSHistory::LoadURI(const char16_t* aURI,
}
NS_IMETHODIMP
nsSHistory::GotoIndex(int32_t aIndex)
nsSHistory::GotoIndex(int32_t aGlobalIndex)
{
return LoadEntry(aIndex, nsIDocShellLoadInfo::loadHistory,
// We provide abstraction of grouped session history for nsIWebNavigation
// functions, so the index passed in here is global index.
return LoadEntry(aGlobalIndex - mGlobalIndexOffset, nsIDocShellLoadInfo::loadHistory,
HIST_CMD_GOTOINDEX);
}
@ -1540,6 +1633,26 @@ nsSHistory::LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd)
return NS_ERROR_FAILURE;
}
if (aIndex < 0 || aIndex >= mLength) {
if (aIndex + mGlobalIndexOffset < 0) {
// The global index is negative.
return NS_ERROR_FAILURE;
}
if (aIndex - mLength >= mEntriesInFollowingPartialHistories) {
// The global index exceeds max possible value.
return NS_ERROR_FAILURE;
}
// The global index is valid. trigger cross browser navigation.
nsCOMPtr<nsIPartialSHistoryListener> listener =
do_QueryReferent(mPartialHistoryListener);
if (!listener) {
return NS_ERROR_FAILURE;
}
return listener->OnRequestCrossBrowserNavigation(aIndex + mGlobalIndexOffset);
}
// Keep note of requested history index in mRequestedIndex.
mRequestedIndex = aIndex;
@ -1755,6 +1868,26 @@ nsSHistory::GetSHistoryEnumerator(nsISimpleEnumerator** aEnumerator)
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::OnAttachGroupedSessionHistory(int32_t aOffset)
{
NS_ENSURE_TRUE(!mIsPartial, NS_ERROR_UNEXPECTED);
NS_ENSURE_TRUE(aOffset >= 0, NS_ERROR_ILLEGAL_VALUE);
mIsPartial = true;
mGlobalIndexOffset = aOffset;
// The last attached history is always at the end of the group.
mEntriesInFollowingPartialHistories = 0;
// Setting grouped history info may change canGoBack / canGoForward.
// Send a location change to update these values.
NS_DispatchToCurrentThread(NewRunnableMethod(static_cast<nsDocShell*>(mRootDocShell),
&nsDocShell::FireDummyOnLocationChange));
return NS_OK;
}
nsSHEnumerator::nsSHEnumerator(nsSHistory* aSHistory) : mIndex(-1)
{
mSHistory = aSHistory;

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

@ -14,6 +14,7 @@
#include "nsISimpleEnumerator.h"
#include "nsTObserverArray.h"
#include "nsWeakPtr.h"
#include "nsIPartialSHistoryListener.h"
#include "prclist.h"
@ -46,7 +47,7 @@ public:
// Otherwise, it comes straight from the pref.
static uint32_t GetMaxTotalViewers() { return sHistoryMaxTotalViewers; }
protected:
private:
virtual ~nsSHistory();
friend class nsSHEnumerator;
friend class nsSHistoryObserver;
@ -80,7 +81,6 @@ protected:
nsresult LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType,
uint32_t aHistCmd);
protected:
// aIndex is the index of the transaction which may be removed.
// If aKeepNext is true, aIndex is compared to aIndex + 1,
// otherwise comparison is done to aIndex - 1.
@ -90,8 +90,22 @@ protected:
int32_t mIndex;
int32_t mLength;
int32_t mRequestedIndex;
// Set to true if attached to a grouped session history.
bool mIsPartial;
// The number of entries before this session history object.
int32_t mGlobalIndexOffset;
// The number of entries after this session history object.
int32_t mEntriesInFollowingPartialHistories;
// Session History listeners
nsAutoTObserverArray<nsWeakPtr, 2> mListeners;
// Partial session history listener
nsWeakPtr mPartialHistoryListener;
// Weak reference. Do not refcount this.
nsIDocShell* mRootDocShell;

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

@ -0,0 +1,172 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "GroupedSHistory.h"
#include "TabParent.h"
#include "PartialSHistory.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION(GroupedSHistory, mPartialHistories)
NS_IMPL_CYCLE_COLLECTING_ADDREF(GroupedSHistory)
NS_IMPL_CYCLE_COLLECTING_RELEASE(GroupedSHistory)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GroupedSHistory)
NS_INTERFACE_MAP_ENTRY(nsIGroupedSHistory)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGroupedSHistory)
NS_INTERFACE_MAP_END
GroupedSHistory::GroupedSHistory()
: mCount(0),
mIndexOfActivePartialHistory(-1)
{
}
NS_IMETHODIMP
GroupedSHistory::GetCount(uint32_t* aResult)
{
MOZ_ASSERT(aResult);
*aResult = mCount;
return NS_OK;
}
NS_IMETHODIMP
GroupedSHistory::AppendPartialSessionHistory(nsIPartialSHistory* aPartialHistory)
{
if (!aPartialHistory) {
return NS_ERROR_INVALID_POINTER;
}
nsCOMPtr<nsIPartialSHistory> partialHistory(aPartialHistory);
if (!partialHistory || mPartialHistories.Contains(partialHistory)) {
return NS_ERROR_FAILURE;
}
// Remove all items after active one and deactive it, unless it's the first
// call and no active partial history has been set yet.
if (mIndexOfActivePartialHistory >= 0) {
PurgePartialHistories(mIndexOfActivePartialHistory);
nsCOMPtr<nsIPartialSHistory> prevPartialHistory =
mPartialHistories[mIndexOfActivePartialHistory];
if (NS_WARN_IF(!prevPartialHistory)) {
// Cycle collected?
return NS_ERROR_UNEXPECTED;
}
prevPartialHistory->OnDeactive();
}
// Attach the partial history.
uint32_t offset = mCount;
mCount += partialHistory->GetCount();
mPartialHistories.AppendElement(partialHistory);
partialHistory->OnAttachGroupedSessionHistory(offset);
mIndexOfActivePartialHistory = mPartialHistories.Count() - 1;
return NS_OK;
}
NS_IMETHODIMP
GroupedSHistory::OnPartialSessionHistoryChange(
nsIPartialSHistory* aPartialSessionHistory)
{
if (!aPartialSessionHistory) {
return NS_ERROR_INVALID_POINTER;
}
nsCOMPtr<nsIPartialSHistory> partialHistory(aPartialSessionHistory);
int32_t index = mPartialHistories.IndexOf(partialHistory);
if (NS_WARN_IF(index != mIndexOfActivePartialHistory) ||
NS_WARN_IF(index < 0)) {
// Non-active or not attached partialHistory
return NS_ERROR_UNEXPECTED;
}
PurgePartialHistories(index);
// Update global count.
uint32_t count = partialHistory->GetCount();
uint32_t offset = partialHistory->GetGlobalIndexOffset();
mCount = count + offset;
return NS_OK;
}
NS_IMETHODIMP
GroupedSHistory::GotoIndex(uint32_t aGlobalIndex,
nsIFrameLoader** aTargetLoaderToSwap)
{
nsCOMPtr<nsIPartialSHistory> currentPartialHistory =
mPartialHistories[mIndexOfActivePartialHistory];
if (!currentPartialHistory) {
// Cycle collected?
return NS_ERROR_UNEXPECTED;
}
for (uint32_t i = 0; i < mPartialHistories.Length(); i++) {
nsCOMPtr<nsIPartialSHistory> partialHistory = mPartialHistories[i];
if (NS_WARN_IF(!partialHistory)) {
// Cycle collected?
return NS_ERROR_UNEXPECTED;
}
// Examine index range.
uint32_t offset = partialHistory->GetGlobalIndexOffset();
uint32_t count = partialHistory->GetCount();
if (offset <= aGlobalIndex && (offset + count) > aGlobalIndex) {
uint32_t targetIndex = aGlobalIndex - offset;
partialHistory->GetOwnerFrameLoader(aTargetLoaderToSwap);
if ((size_t)mIndexOfActivePartialHistory == i) {
return NS_OK;
}
mIndexOfActivePartialHistory = i;
if (NS_FAILED(currentPartialHistory->OnDeactive()) ||
NS_FAILED(partialHistory->OnActive(mCount, targetIndex))) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
}
// Index not found.
NS_WARNING("Out of index request!");
return NS_ERROR_FAILURE;
}
void
GroupedSHistory::PurgePartialHistories(uint32_t aLastPartialIndexToKeep)
{
uint32_t lastIndex = mPartialHistories.Length() - 1;
if (aLastPartialIndexToKeep >= lastIndex) {
// Nothing to remove.
return;
}
// Close tabs.
for (uint32_t i = lastIndex; i > aLastPartialIndexToKeep; i--) {
nsCOMPtr<nsIPartialSHistory> partialHistory = mPartialHistories[i];
if (!partialHistory) {
// Cycle collected?
return;
}
nsCOMPtr<nsIFrameLoader> loader;
partialHistory->GetOwnerFrameLoader(getter_AddRefs(loader));
loader->RequestFrameLoaderClose();
}
// Remove references.
mPartialHistories.RemoveElementsAt(aLastPartialIndexToKeep + 1,
lastIndex - aLastPartialIndexToKeep);
}
/* static */ bool
GroupedSHistory::GroupedHistoryEnabled() {
return Preferences::GetBool("browser.groupedhistory.enabled", false);
}
} // namespace dom
} // namespace mozilla

105
dom/base/GroupedSHistory.h Normal file
Просмотреть файл

@ -0,0 +1,105 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef GroupedSHistory_h
#define GroupedSHistory_h
#include "nsIFrameLoader.h"
#include "nsIGroupedSHistory.h"
#include "nsIPartialSHistory.h"
#include "nsTArray.h"
#include "nsWeakReference.h"
namespace mozilla {
namespace dom {
/**
* GroupedSHistory connects session histories across multiple frameloaders.
* Each frameloader has a PartialSHistory, and GroupedSHistory has an array
* refering to all participating PartialSHistory(s).
*
* The following figure illustrates the idea. In this case, the GroupedSHistory
* is composed of 3 frameloaders, and the active one is frameloader 1.
* GroupedSHistory is always attached to the active frameloader.
*
* +----------------------------------------------------+
* | |
* | v
* +------------------+ +-------------------+ +-----------------+
* | FrameLoader 1 | | PartialSHistory 1 | | GroupedSHistory |
* | (active) |----->| (active) |<--+---| |
* +------------------+ +-------------------+ | +-----------------+
* |
* +------------------+ +-------------------+ |
* | FrameLoader 2 | | PartialSHistory 2 | |
* | (inactive) |----->| (inactive) |<--+
* +------------------+ +-------------------+ |
* |
* +------------------+ +-------------------+ |
* | FrameLoader 3 | | PartialSHistory 3 | |
* | (inactive) |----->| (inactive) |<--+
* +------------------+ +-------------------+
*
* If a history navigation leads to frameloader 3, it becomes the active one,
* and GroupedSHistory is re-attached to frameloader 3.
*
* +------------------+ +-------------------+
* | FrameLoader 1 | | PartialSHistory 1 |
* | (inactive) |----->| (inactive) |<--+
* +------------------+ +-------------------+ |
* |
* +------------------+ +-------------------+ |
* | FrameLoader 2 | | PartialSHistory 2 | |
* | (inactive) |----->| (inactive) |<--+
* +------------------+ +-------------------+ |
* |
* +------------------+ +-------------------+ | +-----------------+
* | FrameLoader 3 | | PartialSHistory 3 | | | GroupedSHistory |
* | (active) |----->| (active) |<--+---| |
* +------------------+ +-------------------+ +-----------------+
* | ^
* | |
* +----------------------------------------------------+
*/
class GroupedSHistory final : public nsIGroupedSHistory
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(GroupedSHistory)
NS_DECL_NSIGROUPEDSHISTORY
GroupedSHistory();
/**
* Get the value of preference "browser.groupedhistory.enabled" to determine
* if grouped session history should be enabled.
*/
static bool GroupedHistoryEnabled();
private:
~GroupedSHistory() {}
/**
* Remove all partial histories and close tabs after the given index (of
* mPartialHistories, not the index of session history entries).
*/
void PurgePartialHistories(uint32_t aLastPartialIndexToKeep);
// The total number of entries in all partial histories.
uint32_t mCount;
// The index of currently active partial history in mPartialHistories.
// Use int32_t as we have invalid index and nsCOMArray also uses int32_t.
int32_t mIndexOfActivePartialHistory;
// All participating nsIPartialSHistory objects.
nsCOMArray<nsIPartialSHistory> mPartialHistories;
};
} // namespace dom
} // namespace mozilla
#endif /* GroupedSHistory_h */

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

@ -0,0 +1,292 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "PartialSHistory.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION(PartialSHistory, mOwnerFrameLoader)
NS_IMPL_CYCLE_COLLECTING_ADDREF(PartialSHistory)
NS_IMPL_CYCLE_COLLECTING_RELEASE(PartialSHistory)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PartialSHistory)
NS_INTERFACE_MAP_ENTRY(nsIPartialSHistory)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPartialSHistory)
NS_INTERFACE_MAP_ENTRY(nsISHistoryListener)
NS_INTERFACE_MAP_ENTRY(nsIPartialSHistoryListener)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END
PartialSHistory::PartialSHistory(nsIFrameLoader* aOwnerFrameLoader)
: mCount(0),
mGlobalIndexOffset(0),
mOwnerFrameLoader(aOwnerFrameLoader)
{
MOZ_ASSERT(aOwnerFrameLoader);
}
already_AddRefed<nsISHistory>
PartialSHistory::GetSessionHistory()
{
if (!mOwnerFrameLoader) {
// Cycle collected?
return nullptr;
}
nsCOMPtr<nsIDocShell> docShell;
mOwnerFrameLoader->GetDocShell(getter_AddRefs(docShell));
if (!docShell) {
return nullptr;
}
nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell));
nsCOMPtr<nsISHistory> shistory;
webNav->GetSessionHistory(getter_AddRefs(shistory));
return shistory.forget();
}
already_AddRefed<TabParent>
PartialSHistory::GetTabParent()
{
if (!mOwnerFrameLoader) {
// Cycle collected?
return nullptr;
}
nsCOMPtr<nsITabParent> tabParent;
mOwnerFrameLoader->GetTabParent(getter_AddRefs(tabParent));
return RefPtr<TabParent>(static_cast<TabParent*>(tabParent.get())).forget();
}
NS_IMETHODIMP
PartialSHistory::GetCount(uint32_t* aResult)
{
if (!aResult) {
return NS_ERROR_INVALID_POINTER;
}
// If we have direct reference to nsISHistory, simply pass through.
nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
if (shistory) {
int32_t count;
nsresult rv = shistory->GetCount(&count);
if (NS_FAILED(rv) || count < 0) {
*aResult = 0;
return NS_ERROR_FAILURE;
}
*aResult = count;
return NS_OK;
}
// Otherwise use the cached value.
*aResult = mCount;
return NS_OK;
}
NS_IMETHODIMP
PartialSHistory::GetGlobalIndexOffset(uint32_t* aResult)
{
if (!aResult) {
return NS_ERROR_INVALID_POINTER;
}
// If we have direct reference to nsISHistory, simply pass through.
nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
if (shistory) {
int32_t offset;
nsresult rv = shistory->GetGlobalIndexOffset(&offset);
if (NS_FAILED(rv) || offset < 0) {
*aResult = 0;
return NS_ERROR_FAILURE;
}
*aResult = offset;
return NS_OK;
}
// Otherwise use the cached value.
*aResult = mGlobalIndexOffset;
return NS_OK;
}
NS_IMETHODIMP
PartialSHistory::GetOwnerFrameLoader(nsIFrameLoader** aResult)
{
nsCOMPtr<nsIFrameLoader> loader(mOwnerFrameLoader);
loader.forget(aResult);
return NS_OK;
}
NS_IMETHODIMP
PartialSHistory::OnAttachGroupedSessionHistory(uint32_t aOffset)
{
mGlobalIndexOffset = aOffset;
// If we have direct reference to nsISHistory, simply pass through.
nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
if (shistory) {
// nsISHistory uses int32_t
if (aOffset > INT32_MAX) {
return NS_ERROR_FAILURE;
}
return shistory->OnAttachGroupedSessionHistory(aOffset);
}
// Otherwise notify through TabParent.
RefPtr<TabParent> tabParent(GetTabParent());
if (!tabParent) {
// We have neither shistory nor tabParent?
NS_WARNING("Unable to get shitory nor tabParent!");
return NS_ERROR_UNEXPECTED;
}
Unused << tabParent->SendNotifyAttachGroupedSessionHistory(aOffset);
return NS_OK;
}
NS_IMETHODIMP
PartialSHistory::OnSessionHistoryChange(uint32_t aCount)
{
mCount = aCount;
return OnLengthChange(aCount);
}
NS_IMETHODIMP
PartialSHistory::OnActive(uint32_t aGlobalLength, uint32_t aTargetLocalIndex)
{
// In-process case.
nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
if (shistory) {
// nsISHistory uses int32_t
if (aGlobalLength > INT32_MAX || aTargetLocalIndex > INT32_MAX) {
return NS_ERROR_FAILURE;
}
return shistory->OnPartialSessionHistoryActive(aGlobalLength,
aTargetLocalIndex);
}
// Cross-process case.
RefPtr<TabParent> tabParent(GetTabParent());
if (!tabParent) {
// We have neither shistory nor tabParent?
NS_WARNING("Unable to get shitory nor tabParent!");
return NS_ERROR_UNEXPECTED;
}
Unused << tabParent->SendNotifyPartialSessionHistoryActive(aGlobalLength,
aTargetLocalIndex);
return NS_OK;
}
NS_IMETHODIMP
PartialSHistory::OnDeactive()
{
// In-process case.
nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
if (shistory) {
if (NS_FAILED(shistory->OnPartialSessionHistoryDeactive())) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
// Cross-process case.
RefPtr<TabParent> tabParent(GetTabParent());
if (!tabParent) {
// We have neither shistory nor tabParent?
NS_WARNING("Unable to get shitory nor tabParent!");
return NS_ERROR_UNEXPECTED;
}
Unused << tabParent->SendNotifyPartialSessionHistoryDeactive();
return NS_OK;
}
/*******************************************************************************
* nsIPartialSHistoryListener
******************************************************************************/
NS_IMETHODIMP
PartialSHistory::OnRequestCrossBrowserNavigation(uint32_t aIndex)
{
if (!mOwnerFrameLoader) {
// Cycle collected?
return NS_ERROR_UNEXPECTED;
}
return mOwnerFrameLoader->RequestGroupedHistoryNavigation(aIndex);
}
/*******************************************************************************
* nsISHistoryListener
******************************************************************************/
NS_IMETHODIMP
PartialSHistory::OnLengthChange(int32_t aCount)
{
if (!mOwnerFrameLoader) {
// Cycle collected?
return NS_ERROR_UNEXPECTED;
}
if (aCount < 0) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIGroupedSHistory> groupedHistory;
mOwnerFrameLoader->GetGroupedSessionHistory(getter_AddRefs(groupedHistory));
if (!groupedHistory) {
// Maybe we're not the active partial history, but in this case we shouldn't
// receive any update from session history object either.
return NS_ERROR_FAILURE;
}
groupedHistory->OnPartialSessionHistoryChange(this);
return NS_OK;
}
NS_IMETHODIMP
PartialSHistory::OnHistoryNewEntry(nsIURI *aNewURI, int32_t aOldIndex)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
PartialSHistory::OnHistoryGoBack(nsIURI *aBackURI, bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
PartialSHistory::OnHistoryGoForward(nsIURI *aForwardURI, bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
PartialSHistory::OnHistoryReload(nsIURI *aReloadURI, uint32_t aReloadFlags, bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
PartialSHistory::OnHistoryGotoIndex(int32_t aIndex, nsIURI *aGotoURI, bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
PartialSHistory::OnHistoryPurge(int32_t aNumEntries, bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
PartialSHistory::OnHistoryReplaceEntry(int32_t aIndex)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
} // namespace dom
} // namespace mozilla

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

@ -0,0 +1,61 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef PartialSHistory_h
#define PartialSHistory_h
#include "nsCycleCollectionParticipant.h"
#include "nsFrameLoader.h"
#include "nsIGroupedSHistory.h"
#include "nsIPartialSHistoryListener.h"
#include "nsIPartialSHistory.h"
#include "nsISHistory.h"
#include "nsISHistoryListener.h"
#include "TabParent.h"
namespace mozilla {
namespace dom {
class PartialSHistory final : public nsIPartialSHistory,
public nsISHistoryListener,
public nsIPartialSHistoryListener,
public nsSupportsWeakReference
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PartialSHistory, nsIPartialSHistory)
NS_DECL_NSIPARTIALSHISTORY
NS_DECL_NSIPARTIALSHISTORYLISTENER
NS_DECL_NSISHISTORYLISTENER
/**
* Note that PartialSHistory must be constructed after frameloader has
* created a valid docshell or tabparent.
*/
explicit PartialSHistory(nsIFrameLoader* aOwnerFrameLoader);
private:
~PartialSHistory() {}
already_AddRefed<nsISHistory> GetSessionHistory();
already_AddRefed<TabParent> GetTabParent();
// The cache of number of entries in corresponding nsISHistory. It's only
// used for remote process case. If nsISHistory is in-process, mCount will not
// be used at all.
uint32_t mCount;
// The cache of globalIndexOffset in corresponding nsISHistory. It's only
// used for remote process case.
uint32_t mGlobalIndexOffset;
// The frameloader which owns this PartialSHistory.
nsCOMPtr<nsIFrameLoader> mOwnerFrameLoader;
};
} // namespace dom
} // namespace mozilla
#endif /* PartialSHistory_h */

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

@ -183,6 +183,7 @@ EXPORTS.mozilla.dom += [
'FormData.h',
'FragmentOrElement.h',
'FromParser.h',
'GroupedSHistory.h',
'ImageEncoder.h',
'ImageTracker.h',
'ImportManager.h',
@ -195,6 +196,7 @@ EXPORTS.mozilla.dom += [
'NodeInfo.h',
'NodeInfoInlines.h',
'NodeIterator.h',
'PartialSHistory.h',
'ProcessGlobal.h',
'ResponsiveImageSelector.h',
'SameProcessMessageQueue.h',
@ -247,6 +249,7 @@ UNIFIED_SOURCES += [
'FileReader.cpp',
'FormData.cpp',
'FragmentOrElement.cpp',
'GroupedSHistory.cpp',
'ImageEncoder.cpp',
'ImageTracker.cpp',
'ImportManager.cpp',
@ -333,6 +336,7 @@ UNIFIED_SOURCES += [
'nsXHTMLContentSerializer.cpp',
'nsXMLContentSerializer.cpp',
'nsXMLNameSpaceMap.cpp',
'PartialSHistory.cpp',
'PostMessageEvent.cpp',
'ProcessGlobal.cpp',
'ResponsiveImageSelector.cpp',

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

@ -58,6 +58,8 @@
#include "nsPIWindowRoot.h"
#include "nsLayoutUtils.h"
#include "nsView.h"
#include "GroupedSHistory.h"
#include "PartialSHistory.h"
#include "nsIURI.h"
#include "nsIURL.h"
@ -134,7 +136,13 @@ typedef FrameMetrics::ViewID ViewID;
// we'd need to re-institute a fixed version of bug 98158.
#define MAX_DEPTH_CONTENT_FRAMES 10
NS_IMPL_CYCLE_COLLECTION(nsFrameLoader, mDocShell, mMessageManager, mChildMessageManager, mOpener)
NS_IMPL_CYCLE_COLLECTION(nsFrameLoader,
mDocShell,
mMessageManager,
mChildMessageManager,
mOpener,
mPartialSessionHistory,
mGroupedSessionHistory)
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameLoader)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameLoader)
@ -372,6 +380,117 @@ nsFrameLoader::MakePrerenderedLoaderActive()
return NS_OK;
}
NS_IMETHODIMP
nsFrameLoader::GetPartialSessionHistory(nsIPartialSHistory** aResult)
{
if (mRemoteBrowser && !mPartialSessionHistory) {
// For remote case we can lazy initialize PartialSHistory since
// it doens't need to be registered as a listener to nsISHistory directly.
mPartialSessionHistory = new PartialSHistory(this);
}
nsCOMPtr<nsIPartialSHistory> partialHistory(mPartialSessionHistory);
partialHistory.forget(aResult);
return NS_OK;
}
NS_IMETHODIMP
nsFrameLoader::GetGroupedSessionHistory(nsIGroupedSHistory** aResult)
{
nsCOMPtr<nsIGroupedSHistory> groupedHistory(mGroupedSessionHistory);
groupedHistory.forget(aResult);
return NS_OK;
}
NS_IMETHODIMP
nsFrameLoader::AppendPartialSessionHistoryAndSwap(nsIFrameLoader* aOther)
{
if (!aOther) {
return NS_ERROR_INVALID_POINTER;
}
nsCOMPtr<nsIGroupedSHistory> otherGroupedHistory;
aOther->GetGroupedSessionHistory(getter_AddRefs(otherGroupedHistory));
MOZ_ASSERT(!otherGroupedHistory,
"Cannot append a GroupedSHistory owner to another.");
if (otherGroupedHistory) {
return NS_ERROR_UNEXPECTED;
}
// Append ourselves.
nsresult rv;
if (!mGroupedSessionHistory) {
mGroupedSessionHistory = new GroupedSHistory();
rv = mGroupedSessionHistory->AppendPartialSessionHistory(mPartialSessionHistory);
if (NS_FAILED(rv)) {
return NS_ERROR_FAILURE;
}
}
if (aOther == this) {
return NS_OK;
}
// Append the other.
RefPtr<nsFrameLoader> otherLoader = static_cast<nsFrameLoader*>(aOther);
rv = mGroupedSessionHistory->
AppendPartialSessionHistory(otherLoader->mPartialSessionHistory);
if (NS_FAILED(rv)) {
return NS_ERROR_FAILURE;
}
// Swap loaders through our owner, so the owner's listeners will be correctly
// setup.
nsCOMPtr<nsIBrowser> ourBrowser = do_QueryInterface(mOwnerContent);
nsCOMPtr<nsIBrowser> otherBrowser = do_QueryInterface(otherLoader->mOwnerContent);
if (!ourBrowser || !otherBrowser) {
return NS_ERROR_FAILURE;
}
if (NS_FAILED(ourBrowser->SwapBrowsers(otherBrowser))) {
return NS_ERROR_FAILURE;
}
mGroupedSessionHistory.swap(otherLoader->mGroupedSessionHistory);
return NS_OK;
}
NS_IMETHODIMP
nsFrameLoader::RequestGroupedHistoryNavigation(uint32_t aGlobalIndex)
{
if (!mGroupedSessionHistory) {
return NS_ERROR_UNEXPECTED;
}
nsCOMPtr<nsIFrameLoader> targetLoader;
nsresult rv = mGroupedSessionHistory->
GotoIndex(aGlobalIndex, getter_AddRefs(targetLoader));
if (NS_FAILED(rv)) {
return NS_ERROR_FAILURE;
}
RefPtr<nsFrameLoader> otherLoader = static_cast<nsFrameLoader*>(targetLoader.get());
if (!targetLoader) {
return NS_ERROR_FAILURE;
}
if (targetLoader == this) {
return NS_OK;
}
nsCOMPtr<nsIBrowser> ourBrowser = do_QueryInterface(mOwnerContent);
nsCOMPtr<nsIBrowser> otherBrowser = do_QueryInterface(otherLoader->mOwnerContent);
if (!ourBrowser || !otherBrowser) {
return NS_ERROR_FAILURE;
}
if (NS_FAILED(ourBrowser->SwapBrowsers(otherBrowser))) {
return NS_ERROR_FAILURE;
}
mGroupedSessionHistory.swap(otherLoader->mGroupedSessionHistory);
return NS_OK;
}
nsresult
nsFrameLoader::ReallyStartLoading()
{
@ -2062,6 +2181,15 @@ nsFrameLoader::MaybeCreateDocShell()
nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
webNav->SetSessionHistory(sessionHistory);
if (GroupedSHistory::GroupedHistoryEnabled()) {
mPartialSessionHistory = new PartialSHistory(this);
nsCOMPtr<nsISHistoryListener> listener(do_QueryInterface(mPartialSessionHistory));
nsCOMPtr<nsIPartialSHistoryListener> partialListener(do_QueryInterface(mPartialSessionHistory));
sessionHistory->AddSHistoryListener(listener);
sessionHistory->SetPartialSHistoryListener(partialListener);
}
}
DocShellOriginAttributes attrs;
@ -3124,6 +3252,18 @@ nsFrameLoader::RequestNotifyAfterRemotePaint()
return NS_OK;
}
NS_IMETHODIMP
nsFrameLoader::RequestFrameLoaderClose()
{
nsCOMPtr<nsIBrowser> browser = do_QueryInterface(mOwnerContent);
if (NS_WARN_IF(!browser)) {
// OwnerElement other than nsIBrowser is not supported yet.
return NS_ERROR_NOT_IMPLEMENTED;
}
return browser->CloseBrowser();
}
NS_IMETHODIMP
nsFrameLoader::Print(uint64_t aOuterWindowID,
nsIPrintSettings* aPrintSettings,

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

@ -25,6 +25,7 @@
#include "Units.h"
#include "nsIWebBrowserPersistable.h"
#include "nsIFrame.h"
#include "nsIGroupedSHistory.h"
class nsIURI;
class nsSubDocumentFrame;
@ -370,6 +371,9 @@ private:
// Holds the last known size of the frame.
mozilla::ScreenIntSize mLazySize;
nsCOMPtr<nsIPartialSHistory> mPartialSessionHistory;
nsCOMPtr<nsIGroupedSHistory> mGroupedSessionHistory;
bool mIsPrerendered : 1;
bool mDepthTooGreat : 1;
bool mIsTopLevelContent : 1;

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

@ -18,6 +18,8 @@ interface nsITabParent;
interface nsILoadContext;
interface nsIPrintSettings;
interface nsIWebProgressListener;
interface nsIGroupedSHistory;
interface nsIPartialSHistory;
[scriptable, builtinclass, uuid(1645af04-1bc7-4363-8f2c-eb9679220ab1)]
interface nsIFrameLoader : nsISupports
@ -71,6 +73,17 @@ interface nsIFrameLoader : nsISupports
*/
void makePrerenderedLoaderActive();
/**
* Append partial session history from another frame loader.
*/
void appendPartialSessionHistoryAndSwap(in nsIFrameLoader aOther);
/**
* If grouped session history is applied, use this function to navigate to
* an entry of session history object of another frameloader.
*/
void requestGroupedHistoryNavigation(in unsigned long aGlobalIndex);
/**
* Destroy the frame loader and everything inside it. This will
* clear the weak owner content reference.
@ -138,6 +151,11 @@ interface nsIFrameLoader : nsISupports
*/
void requestNotifyAfterRemotePaint();
/**
* Close the window through the ownerElement.
*/
void requestFrameLoaderClose();
/**
* Print the current document.
*
@ -228,6 +246,17 @@ interface nsIFrameLoader : nsISupports
* to be up-to-date when received.
*/
readonly attribute unsigned long lazyHeight;
/**
* The partial session history.
*/
readonly attribute nsIPartialSHistory partialSessionHistory;
/**
* The grouped session history composed of multiple session history objects
* across root docshells.
*/
readonly attribute nsIGroupedSHistory groupedSessionHistory;
};
%{C++

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

@ -24,6 +24,7 @@ support-files =
window_nsITextInputProcessor.xul
title_window.xul
window_swapFrameLoaders.xul
window_groupedSHistory.xul
[test_bug120684.xul]
[test_bug206691.xul]
@ -72,3 +73,4 @@ skip-if = buildapp == 'mulet'
[test_title.xul]
[test_windowroot.xul]
[test_swapFrameLoaders.xul]
[test_groupedSHistory.xul]

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

@ -0,0 +1,32 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1276553
-->
<window title="Mozilla Bug 1276553"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="loadTest();">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1276553"
target="_blank">Mozilla Bug 1276553</a>
</body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
/** Test for GroupedSHistory **/
SimpleTest.waitForExplicitFinish();
function loadTest() {
window.open("window_groupedSHistory.xul", "", "width=360,height=240,chrome");
}
]]>
</script>
</window>

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

@ -0,0 +1,331 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1276553
-->
<window title="Mozilla Bug 1276553"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="run();">
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components;
Cu.import("resource://testing-common/TestUtils.jsm");
Cu.import("resource://testing-common/ContentTask.jsm");
Cu.import("resource://testing-common/BrowserTestUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
ContentTask.setTestScope(window.opener.wrappedJSObject);
let imports = ['SimpleTest', 'SpecialPowers', 'ok', 'is', 'info'];
for (let name of imports) {
window[name] = window.opener.wrappedJSObject[name];
}
/** Test for Bug 1276553 **/
function run() {
new Promise(resolve => SpecialPowers.pushPrefEnv(
{'set' : [[ 'browser.groupedhistory.enabled', true ]]}, resolve))
.then(() => test(false))
.then(() => test(true))
.then(() => {
window.close();
SimpleTest.finish();
});
}
function test(remote) {
let act, bg1, bg2;
return Promise.resolve()
// create first browser with 1 entry (which will always be the active one)
.then(() => info('TEST-INFO | test create active browser, remote=' + remote))
.then(() => createBrowser('pen', remote))
.then(b => act = b)
.then(() => verifyBrowser(act, 'pen' /* title */,
0 /* index */,
1 /* length */,
false /* canGoBack */,
false /* canGoForward */,
false /* partial */ ))
// create background browser 1 with 1 entry
.then(() => info('TEST-INFO | test create background browser 1, remote=' + remote))
.then(() => createBrowser('pineapple', remote))
.then(b => bg1 = b)
.then(() => verifyBrowser(bg1, 'pineapple' /* title */,
0 /* index */,
1 /* length */,
false /* canGoBack */,
false /* canGoForward */,
false /* partial */ ))
// create background browser 2 with 2 entries
.then(() => info('TEST-INFO | test create background browser 2, remote=' + remote))
.then(() => createBrowser('apple', remote))
.then(b => bg2 = b)
.then(() => verifyBrowser(bg2, 'apple' /* title */,
0 /* index */,
1 /* length */,
false /* canGoBack */,
false /* canGoForward */,
false /* partial */ ))
.then(() => loadURI(bg2, getDummyHtml('pencil')))
.then(() => verifyBrowser(bg2, 'pencil' /* title */,
1 /* index */,
2 /* length */,
true /* canGoBack */,
false /* canGoForward */,
false /* partial */ ))
// merge to 2 entries pen-pineapple
.then(() => info('TEST-INFO | test merge history, remote=' + remote))
.then(() => mergeHistory(act, bg1))
.then(() => verifyBrowser(act, 'pineapple' /* title */,
0 /* index */,
1 /* length */,
true /* canGoBack */,
false /* canGoForward */,
true /* partial */,
1 /* offset */,
2 /* globalLength */ ))
// merge to 4 entries pen-pineapple-apple-pencil
.then(() => mergeHistory(act, bg2))
.then(() => verifyBrowser(act, 'pencil' /* title */,
1 /* index */,
2 /* length */,
true /* canGoBack */,
false /* canGoForward */,
true /* partial */,
2 /* offset */,
4 /* globalLength */ ))
// test go back
.then(() => info('TEST-INFO | test history go back, remote=' + remote))
.then(() => wrapHistoryNavFn(act, act.goBack.bind(act)))
.then(() => verifyBrowser(act, 'apple' /* title */,
0 /* index */,
2 /* length */,
true /* canGoBack */,
true /* canGoForward */,
true /* partial */,
2 /* offset */,
4 /* globalLength */ ))
// XXX The 2nd pageshow comes from reload as current index of the active
// partial history remains the same
.then(() => wrapHistoryNavFn(act, act.goBack.bind(act), true))
.then(() => verifyBrowser(act, 'pineapple' /* title */,
0 /* index */,
1 /* length */,
true /* canGoBack */,
true /* canGoForward */,
true /* partial */,
1 /* offset */,
4 /* globalLength */ ))
.then(() => wrapHistoryNavFn(act, act.goBack.bind(act), true))
.then(() => verifyBrowser(act, 'pen' /* title */,
0 /* index */,
1 /* length */,
false /* canGoBack */,
true /* canGoForward */,
true /* partial */,
0 /* offset */,
4 /* globalLength */ ))
// test go forward
.then(() => info('TEST-INFO | test history go forward, remote=' + remote))
.then(() => wrapHistoryNavFn(act, act.goForward.bind(act), true))
.then(() => verifyBrowser(act, 'pineapple' /* title */,
0 /* index */,
1 /* length */,
true /* canGoBack */,
true /* canGoForward */,
true /* partial */,
1 /* offset */,
4 /* globalLength */ ))
.then(() => wrapHistoryNavFn(act, act.goForward.bind(act), true))
.then(() => verifyBrowser(act, 'apple' /* title */,
0 /* index */,
2 /* length */,
true /* canGoBack */,
true /* canGoForward */,
true /* partial */,
2 /* offset */,
4 /* globalLength */ ))
.then(() => wrapHistoryNavFn(act, act.goForward.bind(act)))
.then(() => verifyBrowser(act, 'pencil' /* title */,
1 /* index */,
2 /* length */,
true /* canGoBack */,
false /* canGoForward */,
true /* partial */,
2 /* offset */,
4 /* globalLength */ ))
// test goto index
.then(() => info('TEST-INFO | test history goto index, remote=' + remote))
.then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 0), true))
.then(() => verifyBrowser(act, 'pen' /* title */,
0 /* index */,
1 /* length */,
false /* canGoBack */,
true /* canGoForward */,
true /* partial */,
0 /* offset */,
4 /* globalLength */ ))
// expect 2 pageshow since we're also changing mIndex of the partial history
.then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 2), true, 2))
.then(() => verifyBrowser(act, 'apple' /* title */,
0 /* index */,
2 /* length */,
true /* canGoBack */,
true /* canGoForward */,
true /* partial */,
2 /* offset */,
4 /* globalLength */ ))
.then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 1), true))
.then(() => verifyBrowser(act, 'pineapple' /* title */,
0 /* index */,
1 /* length */,
true /* canGoBack */,
true /* canGoForward */,
true /* partial */,
1 /* offset */,
4 /* globalLength */ ))
// expect 2 pageshow since we're also changing mIndex of the partial history
.then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 3), true, 2))
.then(() => verifyBrowser(act, 'pencil' /* title */,
1 /* index */,
2 /* length */,
true /* canGoBack */,
false /* canGoForward */,
true /* partial */,
2 /* offset */,
4 /* globalLength */ ))
// test history change to 3 entries pen-pineapple-banana
.then(() => info('TEST-INFO | test history change, remote=' + remote))
.then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 1), true))
.then(() => verifyBrowser(act, 'pineapple' /* title */,
0 /* index */,
1 /* length */,
true /* canGoBack */,
true /* canGoForward */,
true /* partial */,
1 /* offset */,
4 /* globalLength */ ))
.then(() => loadURI(act, getDummyHtml('banana')))
.then(() => verifyBrowser(act, 'banana' /* title */,
1 /* index */,
2 /* length */,
true /* canGoBack */,
false /* canGoForward */,
true /* partial */,
1 /* offset */,
3 /* globalLength */ ))
}
function getDummyHtml(title) {
return 'data:text/html;charset=UTF-8,' +
'<html><head><title>' + title + '</title></head></html>'
}
function createBrowser(title, remote) {
let browser = document.createElement('browser');
browser.setAttribute('type', 'content');
browser.setAttribute('remote', remote);
browser.setAttribute('src', getDummyHtml(title));
document.getElementById('stack').appendChild(browser);
return BrowserTestUtils.browserLoaded(browser)
.then(() => {
browser.messageManager.loadFrameScript('data:,' +
'addEventListener("pageshow", () => sendAsyncMessage("test:pageshow", null), false);' +
'addEventListener("pagehide", () => sendAsyncMessage("test:pagehide", null), false);',
true);
})
.then(() => {
// a trick to ensure webProgress object is created for e10s case
ok(browser.webProgress, 'check browser.webProgress exists');
return browser;
});
}
function loadURI(browser, uri) {
let promise = BrowserTestUtils.browserLoaded(browser, false, uri);
browser.loadURI(uri);
return promise;
}
function mergeHistory(b1, b2) {
let promises = [];
let pagehide1, pagehide2;
// For swapping there should be a pagehide followed by a pageshow.
promises.push(BrowserTestUtils.waitForMessage(b1.messageManager, 'test:pagehide', msg => pagehide1 = true));
promises.push(BrowserTestUtils.waitForMessage(b2.messageManager, 'test:pagehide', msg => pagehide2 = true));
promises.push(BrowserTestUtils.waitForMessage(b1.messageManager, 'test:pageshow', msg => pagehide1));
promises.push(BrowserTestUtils.waitForMessage(b2.messageManager, 'test:pageshow', msg => pagehide2));
promises.push(Promise.resolve().then(() => {
let f1 = b1.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
let f2 = b2.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
f1.appendPartialSessionHistoryAndSwap(f2);
}));
return Promise.all(promises);
}
function wrapHistoryNavFn(browser, navFn, expectSwap = false, expectPageshowCount = 1) {
let promises = [];
let pagehide = false;
let pageshowCount = 0;
if (expectSwap) {
// For swapping there should be a pagehide followed by a pageshow.
promises.push(BrowserTestUtils.waitForMessage(browser.messageManager,
'test:pagehide', msg => pagehide = true));
}
promises.push(BrowserTestUtils.waitForMessage(browser.messageManager,
'test:pageshow', msg => {
// Only count events after pagehide for swapping case.
if (!expectSwap || pagehide) {
return !--expectPageshowCount;
}
return false;
}));
promises.push(Task.spawn(navFn));
return Promise.all(promises);
}
function verifyBrowser(browser, title, index, length, canGoBack, canGoForward,
partial, offset = 0, globalLength = length) {
is(browser.canGoBack, canGoBack, 'check browser.canGoBack');
is(browser.canGoForward, canGoForward, 'check browser.canGoForward');
if (partial) {
let frameLoader = browser.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
is(frameLoader.groupedSessionHistory.count, globalLength, 'check groupedSHistory.count');
}
return ContentTask.spawn(browser,
{ title, index, length, canGoBack, canGoForward, partial, offset, globalLength },
({ title, index, length, canGoBack, canGoForward, partial, offset, globalLength }) => {
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
let shistory = webNav.sessionHistory;
is(content.document.title, title, 'check title');
is(webNav.canGoBack, canGoBack, 'check webNav.canGoBack');
is(webNav.canGoForward, canGoForward, 'check webNav.canGoForward');
is(shistory.index, index, 'check shistory.index');
is(shistory.count, length, 'check shistory.count');
is(shistory.isPartial, partial, 'check shistory.isPartial');
is(shistory.globalIndexOffset, offset, 'check shistory.globalIndexOffset');
is(shistory.globalCount, globalLength, 'check shistory.globalCount');
});
}
]]>
</script>
<stack id="stack" flex="1" />
</window>

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

@ -6,6 +6,7 @@
XPIDL_SOURCES += [
'domstubs.idl',
'nsIBrowser.idl',
'nsIBrowserDOMWindow.idl',
'nsIContentPermissionPrompt.idl',
'nsIContentPrefService.idl',

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

@ -24,4 +24,23 @@ interface nsIBrowser : nsISupports
*/
void dropLinks(in unsigned long linksCount,
[array, size_is(linksCount)] in wstring links);
/**
* Swapping of frameloaders are usually initiated from a frameloader owner
* or other components operating on frameloader owners. This is done by calling
* swapFrameLoaders at MozFrameLoaderOwner webidl interface.
*
* This function aimed to provide the other way around -
* if the swapping is initiated from frameloader itself or other platform level
* components, it uses this interface to delegate the swapping request to
* frameloader owners and ask them to re-initiate frameloader swapping, so that
* frameloader owners such as <xul:browser> can setup their properties and /
* or listeners properly on swapping.
*/
void swapBrowsers(in nsIBrowser aOtherBrowser);
/**
* Close the browser (usually means to remove a tab).
*/
void closeBrowser();
};

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

@ -600,6 +600,23 @@ parent:
// bridge.
sync EnsureLayersConnected();
/**
* Notify parent that one or more entries have been added / removed from
* the child session history.
*
* @param aCount the updated number of entries in child session history
*/
async NotifySessionHistoryChange(uint32_t aCount);
/**
* When the session history is across multiple root docshells, this function
* is used to notify parent that it needs to navigate to an entry out of
* local index of the child.
*
* @param aGlobalIndex The global index of history entry to navigate to.
*/
async RequestCrossBrowserNavigation(uint32_t aGlobalIndex);
child:
/**
* Notify the remote browser that it has been Show()n on this
@ -860,6 +877,32 @@ child:
*/
async UpdateNativeWindowHandle(uintptr_t aNewHandle);
/**
* Called when the session history of this particular PBrowser has been
* attached to a grouped session history.
*
* @param aOffset The number of entries in the grouped session
* history before this session history object.
*/
async NotifyAttachGroupedSessionHistory(uint32_t aOffset);
/**
* Notify that the session history associated to this PBrowser has become
* the active history in the grouped session history.
*
* @param aGlobalLength The up-to-date number of entries in the grouped
* session history.
* @param aTargetLocalIndex The target local index to navigate to.
*/
async NotifyPartialSessionHistoryActive(uint32_t aGlobalLength,
uint32_t aTargetLocalIndex);
/**
* Notify that the session history asssociates to this PBrowser has become
* an inactive history in the grouped session history.
*/
async NotifyPartialSessionHistoryDeactive();
/*
* FIXME: write protocol!

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

@ -116,6 +116,9 @@
#include "FrameLayerBuilder.h"
#include "VRManagerChild.h"
#include "nsICommandParams.h"
#include "nsISHistory.h"
#include "nsQueryObject.h"
#include "GroupedSHistory.h"
#ifdef NS_PRINTING
#include "nsIPrintSession.h"
@ -143,6 +146,10 @@ using namespace mozilla::jsipc;
using mozilla::layers::GeckoContentController;
NS_IMPL_ISUPPORTS(ContentListener, nsIDOMEventListener)
NS_IMPL_ISUPPORTS(TabChildSHistoryListener,
nsISHistoryListener,
nsIPartialSHistoryListener,
nsISupportsWeakReference)
static const CSSSize kDefaultViewportSize(980, 480);
@ -830,6 +837,20 @@ TabChild::Init()
mIPCOpen = true;
if (GroupedSHistory::GroupedHistoryEnabled()) {
// Set session history listener.
nsCOMPtr<nsISHistory> shistory;
mWebNav->GetSessionHistory(getter_AddRefs(shistory));
if (!shistory) {
return NS_ERROR_FAILURE;
}
mHistoryListener = new TabChildSHistoryListener(this);
nsCOMPtr<nsISHistoryListener> listener(do_QueryObject(mHistoryListener));
shistory->AddSHistoryListener(listener);
nsCOMPtr<nsIPartialSHistoryListener> partialListener(do_QueryObject(mHistoryListener));
shistory->SetPartialSHistoryListener(partialListener);
}
return NS_OK;
}
@ -1292,12 +1313,16 @@ TabChild::ActorDestroy(ActorDestroyReason why)
TabChild::~TabChild()
{
DestroyWindow();
DestroyWindow();
nsCOMPtr<nsIWebBrowser> webBrowser = do_QueryInterface(WebNavigation());
if (webBrowser) {
webBrowser->SetContainerWindow(nullptr);
}
nsCOMPtr<nsIWebBrowser> webBrowser = do_QueryInterface(WebNavigation());
if (webBrowser) {
webBrowser->SetContainerWindow(nullptr);
}
if (mHistoryListener) {
mHistoryListener->ClearTabChild();
}
}
void
@ -1841,6 +1866,48 @@ TabChild::RecvMenuKeyboardListenerInstalled(const bool& aInstalled)
return true;
}
bool
TabChild::RecvNotifyAttachGroupedSessionHistory(const uint32_t& aOffset)
{
// nsISHistory uses int32_t
if (NS_WARN_IF(aOffset > INT32_MAX)) {
return false;
}
nsCOMPtr<nsISHistory> shistory;
mWebNav->GetSessionHistory(getter_AddRefs(shistory));
NS_ENSURE_TRUE(shistory, false);
return NS_SUCCEEDED(shistory->OnAttachGroupedSessionHistory(aOffset));
}
bool
TabChild::RecvNotifyPartialSessionHistoryActive(const uint32_t& aGlobalLength,
const uint32_t& aTargetLocalIndex)
{
// nsISHistory uses int32_t
if (NS_WARN_IF(aGlobalLength > INT32_MAX || aTargetLocalIndex > INT32_MAX)) {
return false;
}
nsCOMPtr<nsISHistory> shistory;
mWebNav->GetSessionHistory(getter_AddRefs(shistory));
NS_ENSURE_TRUE(shistory, false);
return NS_SUCCEEDED(shistory->OnPartialSessionHistoryActive(aGlobalLength,
aTargetLocalIndex));
}
bool
TabChild::RecvNotifyPartialSessionHistoryDeactive()
{
nsCOMPtr<nsISHistory> shistory;
mWebNav->GetSessionHistory(getter_AddRefs(shistory));
NS_ENSURE_TRUE(shistory, false);
return NS_SUCCEEDED(shistory->OnPartialSessionHistoryDeactive());
}
bool
TabChild::RecvMouseEvent(const nsString& aType,
const float& aX,
@ -3345,6 +3412,80 @@ TabChild::ForcePaint(uint64_t aLayerObserverEpoch)
RecvSetDocShellIsActive(true, false, aLayerObserverEpoch);
}
/*******************************************************************************
* nsISHistoryListener
******************************************************************************/
NS_IMETHODIMP
TabChildSHistoryListener::OnHistoryNewEntry(nsIURI *aNewURI, int32_t aOldIndex)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
TabChildSHistoryListener::OnHistoryGoBack(nsIURI *aBackURI, bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
TabChildSHistoryListener::OnHistoryGoForward(nsIURI *aForwardURI, bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
TabChildSHistoryListener::OnHistoryReload(nsIURI *aReloadURI, uint32_t aReloadFlags, bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
TabChildSHistoryListener::OnHistoryGotoIndex(int32_t aIndex, nsIURI *aGotoURI, bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
TabChildSHistoryListener::OnHistoryPurge(int32_t aNumEntries, bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
TabChildSHistoryListener::OnHistoryReplaceEntry(int32_t aIndex)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
TabChildSHistoryListener::OnLengthChange(int32_t aCount)
{
RefPtr<TabChild> tabChild(mTabChild);
if (!tabChild) {
return NS_ERROR_FAILURE;
}
if (aCount < 0) {
return NS_ERROR_FAILURE;
}
return tabChild->SendNotifySessionHistoryChange(aCount) ?
NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
TabChildSHistoryListener::OnRequestCrossBrowserNavigation(uint32_t aIndex)
{
RefPtr<TabChild> tabChild(mTabChild);
if (!tabChild) {
return NS_ERROR_FAILURE;
}
return tabChild->SendRequestCrossBrowserNavigation(aIndex) ?
NS_OK : NS_ERROR_FAILURE;
}
TabChildGlobal::TabChildGlobal(TabChildBase* aTabChild)
: mTabChild(aTabChild)
{

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

@ -38,6 +38,8 @@
#include "AudioChannelService.h"
#include "PuppetWidget.h"
#include "mozilla/layers/GeckoContentController.h"
#include "nsISHistoryListener.h"
#include "nsIPartialSHistoryListener.h"
class nsICachedFileDescriptorListener;
class nsIDOMWindowUtils;
@ -165,6 +167,27 @@ protected:
TabChild* mTabChild;
};
/**
* Listens on session history change, and sends NotifySessionHistoryChange to
* parent process.
*/
class TabChildSHistoryListener final : public nsISHistoryListener,
public nsIPartialSHistoryListener,
public nsSupportsWeakReference
{
public:
explicit TabChildSHistoryListener(TabChild* aTabChild) : mTabChild(aTabChild) {}
void ClearTabChild() { mTabChild = nullptr; }
NS_DECL_ISUPPORTS
NS_DECL_NSISHISTORYLISTENER
NS_DECL_NSIPARTIALSHISTORYLISTENER
private:
~TabChildSHistoryListener() {}
TabChild* mTabChild;
};
// This is base clase which helps to share Viewport and touch related
// functionality between b2g/android FF/embedlite clients implementation.
// It make sense to place in this class all helper functions, and functionality
@ -682,6 +705,13 @@ protected:
virtual bool RecvMenuKeyboardListenerInstalled(
const bool& aInstalled) override;
virtual bool RecvNotifyAttachGroupedSessionHistory(const uint32_t& aOffset) override;
virtual bool RecvNotifyPartialSessionHistoryActive(const uint32_t& aGlobalLength,
const uint32_t& aTargetLocalIndex) override;
virtual bool RecvNotifyPartialSessionHistoryDeactive() override;
private:
void HandleDoubleTap(const CSSPoint& aPoint, const Modifiers& aModifiers,
const ScrollableLayerGuid& aGuid);
@ -734,6 +764,7 @@ private:
nsCOMPtr<nsIURI> mLastURI;
RenderFrameChild* mRemoteFrame;
RefPtr<nsIContentChild> mManager;
RefPtr<TabChildSHistoryListener> mHistoryListener;
uint32_t mChromeFlags;
int32_t mActiveSuppressDisplayport;
uint64_t mLayersId;

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

@ -100,6 +100,8 @@
#include "UnitTransforms.h"
#include <algorithm>
#include "mozilla/WebBrowserPersistDocumentParent.h"
#include "nsIGroupedSHistory.h"
#include "PartialSHistory.h"
#if defined(XP_WIN) && defined(ACCESSIBILITY)
#include "mozilla/a11y/AccessibleWrap.h"
@ -3463,6 +3465,40 @@ TabParent::RecvLookUpDictionary(const nsString& aText,
return true;
}
bool
TabParent::RecvNotifySessionHistoryChange(const uint32_t& aCount)
{
RefPtr<nsFrameLoader> frameLoader(GetFrameLoader());
if (!frameLoader) {
// FrameLoader can be nullptr if the it is destroying.
// In this case session history change can simply be ignored.
return true;
}
nsCOMPtr<nsIPartialSHistory> partialHistory;
frameLoader->GetPartialSessionHistory(getter_AddRefs(partialHistory));
if (!partialHistory) {
// PartialSHistory is not enabled
return true;
}
partialHistory->OnSessionHistoryChange(aCount);
return true;
}
bool
TabParent::RecvRequestCrossBrowserNavigation(const uint32_t& aGlobalIndex)
{
RefPtr<nsFrameLoader> frameLoader(GetFrameLoader());
if (!frameLoader) {
// FrameLoader can be nullptr if the it is destroying.
// In this case we can ignore the request.
return true;
}
return NS_SUCCEEDED(frameLoader->RequestGroupedHistoryNavigation(aGlobalIndex));
}
NS_IMETHODIMP
FakeChannel::OnAuthAvailable(nsISupports *aContext, nsIAuthInformation *aAuthInfo)
{

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

@ -33,6 +33,7 @@
#include "nsWeakReference.h"
#include "Units.h"
#include "nsIWidget.h"
#include "nsIPartialSHistory.h"
class nsFrameLoader;
class nsIFrameLoader;
@ -632,6 +633,10 @@ protected:
virtual bool RecvAudioChannelActivityNotification(const uint32_t& aAudioChannel,
const bool& aActive) override;
virtual bool RecvNotifySessionHistoryChange(const uint32_t& aCount) override;
virtual bool RecvRequestCrossBrowserNavigation(const uint32_t& aGlobalIndex) override;
ContentCacheInParent mContentCache;
nsIntRect mRect;

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

@ -5,7 +5,6 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
XPIDL_SOURCES += [
'nsIBrowser.idl',
'nsIHangReport.idl',
]

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

@ -219,6 +219,8 @@ public:
virtual void HandleResumeVideoDecoding();
virtual void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) {}
virtual void DumpDebugInfo() {}
protected:
@ -630,6 +632,14 @@ public:
}
}
void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) override
{
if (aPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
// Schedule Step() to check if we can start playback.
mMaster->ScheduleStateMachine();
}
}
void DumpDebugInfo() override
{
SDUMP("mIsPrerolling=%d", mIsPrerolling);
@ -1051,6 +1061,14 @@ public:
// Do nothing since no decoding is going on.
}
void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) override
{
if (aPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
// Schedule Step() to check if we can start playback.
mMaster->ScheduleStateMachine();
}
}
private:
bool mSentPlaybackEndedEvent = false;
};
@ -2457,34 +2475,15 @@ void MediaDecoderStateMachine::PlayStateChanged()
if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING) {
mVideoDecodeSuspendTimer.Reset();
return;
}
// Once we start playing, we don't want to minimize our prerolling, as we
// assume the user is likely to want to keep playing in future. This needs to
// happen before we invoke StartDecoding().
if (mMinimizePreroll) {
} else if (mMinimizePreroll) {
// Once we start playing, we don't want to minimize our prerolling, as we
// assume the user is likely to want to keep playing in future. This needs to
// happen before we invoke StartDecoding().
mMinimizePreroll = false;
DispatchDecodeTasksIfNeeded();
}
// Some state transitions still happen synchronously on the main thread. So
// if the main thread invokes Play() and then Seek(), the seek will initiate
// synchronously on the main thread, and the asynchronous PlayInternal task
// will arrive when it's no longer valid. The proper thing to do is to move
// all state transitions to the state machine task queue, but for now we just
// make sure that none of the possible main-thread state transitions (Seek(),
// SetDormant(), and Shutdown()) have not occurred.
if (mState != DECODER_STATE_DECODING &&
mState != DECODER_STATE_DECODING_FIRSTFRAME &&
mState != DECODER_STATE_BUFFERING &&
mState != DECODER_STATE_COMPLETED)
{
DECODER_LOG("Unexpected state - Bailing out of PlayInternal()");
return;
}
ScheduleStateMachine();
mStateObj->HandlePlayStateChanged(mPlayState);
}
void MediaDecoderStateMachine::VisibilityChanged()

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

@ -559,11 +559,11 @@ RTCPeerConnection.prototype = {
server.urls.forEach(urlStr => {
let url = nicerNewURI(urlStr);
if (url.scheme in { turn:1, turns:1 }) {
if (!server.username) {
if (server.username == undefined) {
throw new this._win.DOMException(msg + " - missing username: " + urlStr,
"InvalidAccessError");
}
if (!server.credential) {
if (server.credential == undefined) {
throw new this._win.DOMException(msg + " - missing credential: " + urlStr,
"InvalidAccessError");
}

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

@ -37,7 +37,9 @@
#include "mozilla/ClearOnShutdown.h"
#include "nsUnicharUtils.h"
#include "mozilla/dom/MediaSource.h"
#ifdef MOZ_WIDGET_ANDROID
#include "FennecJNIWrappers.h"
#endif
namespace mozilla {
namespace dom {
@ -296,8 +298,8 @@ MediaKeySystemAccess::GetKeySystemStatus(const nsAString& aKeySystem,
}
}
if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) {
if (IsWidevineKeySystem(aKeySystem)) {
if (IsWidevineKeySystem(aKeySystem)) {
if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) {
#ifdef XP_WIN
// Win Vista and later only.
if (!IsVistaOrLater()) {
@ -310,41 +312,51 @@ MediaKeySystemAccess::GetKeySystemStatus(const nsAString& aKeySystem,
return MediaKeySystemStatus::Cdm_disabled;
}
return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage, aOutCdmVersion);
#ifdef MOZ_WIDGET_ANDROID
} else if (Preferences::GetBool("media.mediadrm-widevinecdm.visible", false)) {
nsCString keySystem = NS_ConvertUTF16toUTF8(aKeySystem);
bool supported = mozilla::java::MediaDrmProxy::IsSchemeSupported(keySystem);
if (!supported) {
aOutMessage = NS_LITERAL_CSTRING("Widevine CDM is not available");
return MediaKeySystemStatus::Cdm_not_installed;
}
return MediaKeySystemStatus::Available;
#endif
}
}
return MediaKeySystemStatus::Cdm_not_supported;
}
typedef nsCString GMPCodecString;
typedef nsCString EMECodecString;
#define GMP_CODEC_AAC NS_LITERAL_CSTRING("aac")
#define GMP_CODEC_OPUS NS_LITERAL_CSTRING("opus")
#define GMP_CODEC_VORBIS NS_LITERAL_CSTRING("vorbis")
#define GMP_CODEC_H264 NS_LITERAL_CSTRING("h264")
#define GMP_CODEC_VP8 NS_LITERAL_CSTRING("vp8")
#define GMP_CODEC_VP9 NS_LITERAL_CSTRING("vp9")
static NS_NAMED_LITERAL_CSTRING(EME_CODEC_AAC, "aac");
static NS_NAMED_LITERAL_CSTRING(EME_CODEC_OPUS, "opus");
static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VORBIS, "vorbis");
static NS_NAMED_LITERAL_CSTRING(EME_CODEC_H264, "h264");
static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VP8, "vp8");
static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VP9, "vp9");
GMPCodecString
ToGMPAPICodecString(const nsString& aCodec)
EMECodecString
ToEMEAPICodecString(const nsString& aCodec)
{
if (IsAACCodecString(aCodec)) {
return GMP_CODEC_AAC;
return EME_CODEC_AAC;
}
if (aCodec.EqualsLiteral("opus")) {
return GMP_CODEC_OPUS;
return EME_CODEC_OPUS;
}
if (aCodec.EqualsLiteral("vorbis")) {
return GMP_CODEC_VORBIS;
return EME_CODEC_VORBIS;
}
if (IsH264CodecString(aCodec)) {
return GMP_CODEC_H264;
return EME_CODEC_H264;
}
if (IsVP8CodecString(aCodec)) {
return GMP_CODEC_VP8;
return EME_CODEC_VP8;
}
if (IsVP9CodecString(aCodec)) {
return GMP_CODEC_VP9;
return EME_CODEC_VP9;
}
return EmptyCString();
}
@ -360,18 +372,18 @@ struct KeySystemContainerSupport
// CDM decrypts and decodes using a DRM robust decoder, and passes decoded
// samples back to Gecko for rendering.
bool DecryptsAndDecodes(GMPCodecString aCodec) const
bool DecryptsAndDecodes(EMECodecString aCodec) const
{
return mCodecsDecoded.Contains(aCodec);
}
// CDM decrypts and passes the decrypted samples back to Gecko for decoding.
bool Decrypts(GMPCodecString aCodec) const
bool Decrypts(EMECodecString aCodec) const
{
return mCodecsDecrypted.Contains(aCodec);
}
void SetCanDecryptAndDecode(GMPCodecString aCodec)
void SetCanDecryptAndDecode(EMECodecString aCodec)
{
// Can't both decrypt and decrypt-and-decode a codec.
MOZ_ASSERT(!Decrypts(aCodec));
@ -380,7 +392,7 @@ struct KeySystemContainerSupport
mCodecsDecoded.AppendElement(aCodec);
}
void SetCanDecrypt(GMPCodecString aCodec)
void SetCanDecrypt(EMECodecString aCodec)
{
// Prevent duplicates.
MOZ_ASSERT(!Decrypts(aCodec));
@ -390,8 +402,8 @@ struct KeySystemContainerSupport
}
private:
nsTArray<GMPCodecString> mCodecsDecoded;
nsTArray<GMPCodecString> mCodecsDecrypted;
nsTArray<EMECodecString> mCodecsDecoded;
nsTArray<EMECodecString> mCodecsDecrypted;
};
enum class KeySystemFeatureSupport
@ -414,16 +426,33 @@ struct KeySystemConfig
KeySystemContainerSupport mWebM;
};
StaticAutoPtr<nsTArray<KeySystemConfig>> sKeySystemConfigs;
bool
HavePluginForKeySystem(const nsCString& aKeySystem)
{
bool havePlugin = false;
nsCOMPtr<mozIGeckoMediaPluginService> mps =
do_GetService("@mozilla.org/gecko-media-plugin-service;1");
if (mps) {
havePlugin = HaveGMPFor(mps,
aKeySystem,
NS_LITERAL_CSTRING(GMP_API_DECRYPTOR));
}
#ifdef MOZ_WIDGET_ANDROID
// Check if we can use MediaDrm for this keysystem.
if (!havePlugin) {
havePlugin = mozilla::java::MediaDrmProxy::IsSchemeSupported(aKeySystem);
}
#endif
return havePlugin;
}
static const nsTArray<KeySystemConfig>&
static nsTArray<KeySystemConfig>
GetSupportedKeySystems()
{
if (!sKeySystemConfigs) {
sKeySystemConfigs = new nsTArray<KeySystemConfig>();
ClearOnShutdown(&sKeySystemConfigs);
nsTArray<KeySystemConfig> keySystemConfigs;
{
{
if (HavePluginForKeySystem(kEMEKeySystemClearkey)) {
KeySystemConfig clearkey;
clearkey.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemClearkey);
clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
@ -438,26 +467,28 @@ GetSupportedKeySystems()
#if defined(XP_WIN)
// Clearkey CDM uses WMF decoders on Windows.
if (WMFDecoderModule::HasAAC()) {
clearkey.mMP4.SetCanDecryptAndDecode(GMP_CODEC_AAC);
clearkey.mMP4.SetCanDecryptAndDecode(EME_CODEC_AAC);
} else {
clearkey.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
clearkey.mMP4.SetCanDecrypt(EME_CODEC_AAC);
}
if (WMFDecoderModule::HasH264()) {
clearkey.mMP4.SetCanDecryptAndDecode(GMP_CODEC_H264);
clearkey.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
} else {
clearkey.mMP4.SetCanDecrypt(GMP_CODEC_H264);
clearkey.mMP4.SetCanDecrypt(EME_CODEC_H264);
}
#else
clearkey.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
clearkey.mMP4.SetCanDecrypt(GMP_CODEC_H264);
clearkey.mMP4.SetCanDecrypt(EME_CODEC_AAC);
clearkey.mMP4.SetCanDecrypt(EME_CODEC_H264);
#endif
clearkey.mWebM.SetCanDecrypt(GMP_CODEC_VORBIS);
clearkey.mWebM.SetCanDecrypt(GMP_CODEC_OPUS);
clearkey.mWebM.SetCanDecrypt(GMP_CODEC_VP8);
clearkey.mWebM.SetCanDecrypt(GMP_CODEC_VP9);
sKeySystemConfigs->AppendElement(Move(clearkey));
clearkey.mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
clearkey.mWebM.SetCanDecrypt(EME_CODEC_OPUS);
clearkey.mWebM.SetCanDecrypt(EME_CODEC_VP8);
clearkey.mWebM.SetCanDecrypt(EME_CODEC_VP9);
keySystemConfigs.AppendElement(Move(clearkey));
}
{
}
{
if (HavePluginForKeySystem(kEMEKeySystemWidevine)) {
KeySystemConfig widevine;
widevine.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemWidevine);
widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
@ -466,6 +497,9 @@ GetSupportedKeySystems()
widevine.mPersistentState = KeySystemFeatureSupport::Requestable;
widevine.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
widevine.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
#ifdef MOZ_WIDGET_ANDROID
widevine.mSessionTypes.AppendElement(MediaKeySessionType::Persistent_license);
#endif
widevine.mAudioRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_CRYPTO"));
widevine.mVideoRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_DECODE"));
#if defined(XP_WIN)
@ -475,42 +509,82 @@ GetSupportedKeySystems()
// the Adobe GMP's unencrypted AAC decoding path being used to
// decode content decrypted by the Widevine CDM.
if (WMFDecoderModule::HasAAC()) {
widevine.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
widevine.mMP4.SetCanDecrypt(EME_CODEC_AAC);
}
#elif !defined(MOZ_WIDGET_ANDROID)
widevine.mMP4.SetCanDecrypt(EME_CODEC_AAC);
#endif
#if defined(MOZ_WIDGET_ANDROID)
using namespace mozilla::java;
// MediaDrm.isCryptoSchemeSupported only allows passing
// "video/mp4" or "video/webm" for mimetype string.
// See https://developer.android.com/reference/android/media/MediaDrm.html#isCryptoSchemeSupported(java.util.UUID, java.lang.String)
// for more detail.
typedef struct {
const nsCString& mMimeType;
const nsCString& mEMECodecType;
const char16_t* mCodecType;
KeySystemContainerSupport* mSupportType;
} DataForValidation;
DataForValidation validationList[] = {
{ nsCString("video/mp4"), EME_CODEC_H264, MediaDrmProxy::AVC, &widevine.mMP4 },
{ nsCString("audio/mp4"), EME_CODEC_AAC, MediaDrmProxy::AAC, &widevine.mMP4 },
{ nsCString("video/webm"), EME_CODEC_VP8, MediaDrmProxy::VP8, &widevine.mWebM },
{ nsCString("video/webm"), EME_CODEC_VP9, MediaDrmProxy::VP9, &widevine.mWebM},
{ nsCString("audio/webm"), EME_CODEC_VORBIS, MediaDrmProxy::VORBIS, &widevine.mWebM},
{ nsCString("audio/webm"), EME_CODEC_OPUS, MediaDrmProxy::OPUS, &widevine.mWebM},
};
for (const auto& data: validationList) {
if (MediaDrmProxy::IsCryptoSchemeSupported(kEMEKeySystemWidevine,
data.mMimeType)) {
if (MediaDrmProxy::CanDecode(data.mCodecType)) {
data.mSupportType->SetCanDecryptAndDecode(data.mEMECodecType);
} else {
data.mSupportType->SetCanDecrypt(data.mEMECodecType);
}
}
}
#else
widevine.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
widevine.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
widevine.mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
widevine.mWebM.SetCanDecrypt(EME_CODEC_OPUS);
widevine.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP8);
widevine.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP9);
#endif
widevine.mMP4.SetCanDecryptAndDecode(GMP_CODEC_H264);
widevine.mWebM.SetCanDecrypt(GMP_CODEC_VORBIS);
widevine.mWebM.SetCanDecrypt(GMP_CODEC_OPUS);
widevine.mWebM.SetCanDecryptAndDecode(GMP_CODEC_VP8);
widevine.mWebM.SetCanDecryptAndDecode(GMP_CODEC_VP9);
sKeySystemConfigs->AppendElement(Move(widevine));
keySystemConfigs.AppendElement(Move(widevine));
}
{
}
{
if (HavePluginForKeySystem(kEMEKeySystemPrimetime)) {
KeySystemConfig primetime;
primetime.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemPrimetime);
primetime.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
primetime.mPersistentState = KeySystemFeatureSupport::Required;
primetime.mDistinctiveIdentifier = KeySystemFeatureSupport::Required;
primetime.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
primetime.mMP4.SetCanDecryptAndDecode(GMP_CODEC_AAC);
primetime.mMP4.SetCanDecryptAndDecode(GMP_CODEC_H264);
sKeySystemConfigs->AppendElement(Move(primetime));
primetime.mMP4.SetCanDecryptAndDecode(EME_CODEC_AAC);
primetime.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
keySystemConfigs.AppendElement(Move(primetime));
}
}
return *sKeySystemConfigs;
return keySystemConfigs;
}
static const KeySystemConfig*
GetKeySystemConfig(const nsAString& aKeySystem)
static bool
GetKeySystemConfig(const nsAString& aKeySystem, KeySystemConfig& aOutKeySystemConfig)
{
for (const KeySystemConfig& config : GetSupportedKeySystems()) {
for (auto&& config : GetSupportedKeySystems()) {
if (config.mKeySystem.Equals(aKeySystem)) {
return &config;
aOutKeySystemConfig = mozilla::Move(config);
return true;
}
}
return nullptr;
// No matching key system found.
return false;
}
/* static */
@ -518,9 +592,9 @@ bool
MediaKeySystemAccess::KeySystemSupportsInitDataType(const nsAString& aKeySystem,
const nsAString& aInitDataType)
{
const KeySystemConfig* implementation = GetKeySystemConfig(aKeySystem);
return implementation &&
implementation->mInitDataTypes.Contains(aInitDataType);
KeySystemConfig implementation;
return GetKeySystemConfig(aKeySystem, implementation) &&
implementation.mInitDataTypes.Contains(aInitDataType);
}
enum CodecType
@ -531,28 +605,18 @@ enum CodecType
};
static bool
CanDecryptAndDecode(mozIGeckoMediaPluginService* aGMPService,
const nsString& aKeySystem,
CanDecryptAndDecode(const nsString& aKeySystem,
const nsString& aContentType,
CodecType aCodecType,
const KeySystemContainerSupport& aContainerSupport,
const nsTArray<GMPCodecString>& aCodecs,
const nsTArray<EMECodecString>& aCodecs,
DecoderDoctorDiagnostics* aDiagnostics)
{
MOZ_ASSERT(aCodecType != Invalid);
MOZ_ASSERT(HaveGMPFor(aGMPService,
NS_ConvertUTF16toUTF8(aKeySystem),
NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
for (const GMPCodecString& codec : aCodecs) {
for (const EMECodecString& codec : aCodecs) {
MOZ_ASSERT(!codec.IsEmpty());
nsCString api = (aCodecType == Audio) ? NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER)
: NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER);
if (aContainerSupport.DecryptsAndDecodes(codec) &&
HaveGMPFor(aGMPService,
NS_ConvertUTF16toUTF8(aKeySystem),
api,
codec)) {
if (aContainerSupport.DecryptsAndDecodes(codec)) {
// GMP can decrypt-and-decode this codec.
continue;
}
@ -573,7 +637,7 @@ CanDecryptAndDecode(mozIGeckoMediaPluginService* aGMPService,
// and reject the MediaKeys request, since our policy is to prevent
// the Adobe GMP's unencrypted AAC decoding path being used to
// decode content decrypted by the Widevine CDM.
if (codec == GMP_CODEC_AAC &&
if (codec == EME_CODEC_AAC &&
IsWidevineKeySystem(aKeySystem) &&
!WMFDecoderModule::HasAAC()) {
if (aDiagnostics) {
@ -626,25 +690,25 @@ GetMajorType(const nsAString& aContentType)
}
static CodecType
GetCodecType(const GMPCodecString& aCodec)
GetCodecType(const EMECodecString& aCodec)
{
if (aCodec.Equals(GMP_CODEC_AAC) ||
aCodec.Equals(GMP_CODEC_OPUS) ||
aCodec.Equals(GMP_CODEC_VORBIS)) {
if (aCodec.Equals(EME_CODEC_AAC) ||
aCodec.Equals(EME_CODEC_OPUS) ||
aCodec.Equals(EME_CODEC_VORBIS)) {
return Audio;
}
if (aCodec.Equals(GMP_CODEC_H264) ||
aCodec.Equals(GMP_CODEC_VP8) ||
aCodec.Equals(GMP_CODEC_VP9)) {
if (aCodec.Equals(EME_CODEC_H264) ||
aCodec.Equals(EME_CODEC_VP8) ||
aCodec.Equals(EME_CODEC_VP9)) {
return Video;
}
return Invalid;
}
static bool
AllCodecsOfType(const nsTArray<GMPCodecString>& aCodecs, const CodecType aCodecType)
AllCodecsOfType(const nsTArray<EMECodecString>& aCodecs, const CodecType aCodecType)
{
for (const GMPCodecString& codec : aCodecs) {
for (const EMECodecString& codec : aCodecs) {
if (GetCodecType(codec) != aCodecType) {
return false;
}
@ -686,7 +750,6 @@ IsParameterUnrecognized(const nsAString& aContentType)
// 3.1.2.3 Get Supported Capabilities for Audio/Video Type
static Sequence<MediaKeySystemMediaCapability>
GetSupportedCapabilities(const CodecType aCodecType,
mozIGeckoMediaPluginService* aGMPService,
const nsTArray<MediaKeySystemMediaCapability>& aRequestedCapabilities,
const MediaKeySystemConfiguration& aPartialConfig,
const KeySystemConfig& aKeySystem,
@ -731,10 +794,10 @@ GetSupportedCapabilities(const CodecType aCodecType,
continue;
}
bool invalid = false;
nsTArray<GMPCodecString> codecs;
nsTArray<EMECodecString> codecs;
for (const nsString& codecString : codecStrings) {
GMPCodecString gmpCodec = ToGMPAPICodecString(codecString);
if (gmpCodec.IsEmpty()) {
EMECodecString emeCodec = ToEMEAPICodecString(codecString);
if (emeCodec.IsEmpty()) {
invalid = true;
EME_LOG("MediaKeySystemConfiguration (label='%s') "
"MediaKeySystemMediaCapability('%s','%s') unsupported; "
@ -745,7 +808,7 @@ GetSupportedCapabilities(const CodecType aCodecType,
NS_ConvertUTF16toUTF8(codecString).get());
break;
}
codecs.AppendElement(gmpCodec);
codecs.AppendElement(emeCodec);
}
if (invalid) {
continue;
@ -806,15 +869,15 @@ GetSupportedCapabilities(const CodecType aCodecType,
// Let parameters be that set.
if (isMP4) {
if (aCodecType == Audio) {
codecs.AppendElement(GMP_CODEC_AAC);
codecs.AppendElement(EME_CODEC_AAC);
} else if (aCodecType == Video) {
codecs.AppendElement(GMP_CODEC_H264);
codecs.AppendElement(EME_CODEC_H264);
}
} else if (isWebM) {
if (aCodecType == Audio) {
codecs.AppendElement(GMP_CODEC_VORBIS);
codecs.AppendElement(EME_CODEC_VORBIS);
} else if (aCodecType == Video) {
codecs.AppendElement(GMP_CODEC_VP8);
codecs.AppendElement(EME_CODEC_VP8);
}
}
// Otherwise: Continue to the next iteration.
@ -872,8 +935,7 @@ GetSupportedCapabilities(const CodecType aCodecType,
// robustness and local accumulated configuration in combination with
// restrictions...
const auto& containerSupport = isMP4 ? aKeySystem.mMP4 : aKeySystem.mWebM;
if (!CanDecryptAndDecode(aGMPService,
aKeySystem.mKeySystem,
if (!CanDecryptAndDecode(aKeySystem.mKeySystem,
contentType,
majorType,
containerSupport,
@ -975,8 +1037,7 @@ UnboxSessionTypes(const Optional<Sequence<nsString>>& aSessionTypes)
// 3.1.2.2 Get Supported Configuration and Consent
static bool
GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
const KeySystemConfig& aKeySystem,
GetSupportedConfig(const KeySystemConfig& aKeySystem,
const MediaKeySystemConfiguration& aCandidate,
MediaKeySystemConfiguration& aOutConfig,
DecoderDoctorDiagnostics* aDiagnostics)
@ -1095,7 +1156,6 @@ GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
// and restrictions.
Sequence<MediaKeySystemMediaCapability> caps =
GetSupportedCapabilities(Video,
aGMPService,
aCandidate.mVideoCapabilities,
config,
aKeySystem,
@ -1121,7 +1181,6 @@ GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
// member, accumulated configuration, and restrictions.
Sequence<MediaKeySystemMediaCapability> caps =
GetSupportedCapabilities(Audio,
aGMPService,
aCandidate.mAudioCapabilities,
config,
aKeySystem,
@ -1204,21 +1263,12 @@ MediaKeySystemAccess::GetSupportedConfig(const nsAString& aKeySystem,
MediaKeySystemConfiguration& aOutConfig,
DecoderDoctorDiagnostics* aDiagnostics)
{
nsCOMPtr<mozIGeckoMediaPluginService> mps =
do_GetService("@mozilla.org/gecko-media-plugin-service;1");
if (NS_WARN_IF(!mps)) {
return false;
}
const KeySystemConfig* implementation = nullptr;
if (!HaveGMPFor(mps,
NS_ConvertUTF16toUTF8(aKeySystem),
NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)) ||
!(implementation = GetKeySystemConfig(aKeySystem))) {
KeySystemConfig implementation;
if (!GetKeySystemConfig(aKeySystem, implementation)) {
return false;
}
for (const MediaKeySystemConfiguration& candidate : aConfigs) {
if (mozilla::dom::GetSupportedConfig(mps,
*implementation,
if (mozilla::dom::GetSupportedConfig(implementation,
candidate,
aOutConfig,
aDiagnostics)) {

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

@ -66,6 +66,8 @@ runNetworkTest(() => {
{ urls: ["stun:127.0.0.1", "stun:localhost"] },
{ urls:"stuns:localhost", foo:"" },
{ urls:"turn:[::1]:3478", username:"p", credential:"p" },
{ urls:"turn:[::1]:3478", username:"", credential:"" },
{ urls:"turns:[::1]:3478", username:"", credential:"" },
{ urls:"turn:localhost:3478?transport=udp", username:"p", credential:"p" },
{ urls: ["turn:[::1]:3478", "turn:localhost"], username:"p", credential:"p" },
{ urls:"turns:localhost:3478?transport=udp", username:"p", credential:"p" },

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

@ -1,126 +1,126 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1205983
-->
<head>
<title>Test for Bug 1205983</title>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1205983">Mozilla Bug 1205983</a>
<p id="display"></p>
</div>
<div contenteditable id="de-DE" lang="de-DE" onfocus="deFocus()">German heute ist ein guter Tag</div>
<textarea id="en-US" lang="en-US" onfocus="enFocus()">Nogoodword today is a nice day</textarea>
<pre id="test">
<script class="testbody" type="text/javascript">
function getMisspelledWords(editor) {
return editor.selectionController.getSelection(Components.interfaces.nsISelectionController.SELECTION_SPELLCHECK).toString();
}
var elem_de;
var editor_de;
var selcon_de;
var de_DE;
var hunspell;
/** Test for Bug 1205983 **/
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(function() {
Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
var dir = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)
.get("CurWorkD", Components.interfaces.nsIFile);
dir.append("tests");
dir.append("editor");
dir.append("composer");
dir.append("test");
hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
.getService(Components.interfaces.mozISpellCheckingEngine);
// Install de-DE dictionary.
de_DE = dir.clone();
de_DE.append("de-DE");
is(de_DE.exists(), true, "true expected (de_DE directory should exist)");
hunspell.addDirectory(de_DE);
document.getElementById('de-DE').focus();
});
function deFocus() {
elem_de = document.getElementById('de-DE');
onSpellCheck(elem_de, function () {
var Ci = Components.interfaces;
var editingSession = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIEditingSession);
editor_de = editingSession.getEditorForWindow(window);
selcon_de = editor_de.selectionController;
var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
// Check that we spelled in German, so there is only one misspelled word.
is(sel.toString(), "German", "one misspelled word expected: German");
// Now focus the textarea, which requires English spelling.
document.getElementById('en-US').focus();
});
}
function enFocus() {
var elem_en = document.getElementById('en-US');
var editor_en = elem_en.QueryInterface(Components.interfaces.nsIDOMNSEditableElement).editor;
editor_en.setSpellcheckUserOverride(true);
var inlineSpellChecker = editor_en.getInlineSpellChecker(true);
onSpellCheck(elem_en, function () {
var spellchecker = inlineSpellChecker.spellChecker;
try {
currentDictonary = spellchecker.GetCurrentDictionary();
} catch(e) {}
// Check that the English dictionary is loaded and that the spell check has worked.
is(currentDictonary, "en-US", "expected en-US");
is(getMisspelledWords(editor_en), "Nogoodword", "one misspelled word expected: Nogoodword");
// So far all was boring. The important thing is whether the spell check result
// in the de-DE editor is still the same. After losing focus, no spell check
// updates should take place there.
var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
is(sel.toString(), "German", "one misspelled word expected: German");
// Remove the fake de_DE dictionary again.
hunspell.removeDirectory(de_DE);
// Focus again, so the spelling gets updated, but before we need to kill the focus handler.
elem_de.onfocus = null;
elem_de.blur();
elem_de.focus();
// After removal, the de_DE editor should refresh the spelling with en-US.
onSpellCheck(elem_de, function () {
var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
is(sel.toString(), "heute" + "ist" + "ein" + "guter",
"some misspelled words expected: heute ist ein guter");
// If we don't reset this, we cause massive leaks.
selcon_de = null;
editor_de = null;
SimpleTest.finish();
});
});
}
</script>
</pre>
</body>
</html>
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1205983
-->
<head>
<title>Test for Bug 1205983</title>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1205983">Mozilla Bug 1205983</a>
<p id="display"></p>
</div>
<div contenteditable id="de-DE" lang="de-DE" onfocus="deFocus()">German heute ist ein guter Tag</div>
<textarea id="en-US" lang="en-US" onfocus="enFocus()">Nogoodword today is a nice day</textarea>
<pre id="test">
<script class="testbody" type="text/javascript">
function getMisspelledWords(editor) {
return editor.selectionController.getSelection(Components.interfaces.nsISelectionController.SELECTION_SPELLCHECK).toString();
}
var elem_de;
var editor_de;
var selcon_de;
var de_DE;
var hunspell;
/** Test for Bug 1205983 **/
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(function() {
Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
var dir = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)
.get("CurWorkD", Components.interfaces.nsIFile);
dir.append("tests");
dir.append("editor");
dir.append("composer");
dir.append("test");
hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
.getService(Components.interfaces.mozISpellCheckingEngine);
// Install de-DE dictionary.
de_DE = dir.clone();
de_DE.append("de-DE");
is(de_DE.exists(), true, "true expected (de_DE directory should exist)");
hunspell.addDirectory(de_DE);
document.getElementById('de-DE').focus();
});
function deFocus() {
elem_de = document.getElementById('de-DE');
onSpellCheck(elem_de, function () {
var Ci = Components.interfaces;
var editingSession = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIEditingSession);
editor_de = editingSession.getEditorForWindow(window);
selcon_de = editor_de.selectionController;
var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
// Check that we spelled in German, so there is only one misspelled word.
is(sel.toString(), "German", "one misspelled word expected: German");
// Now focus the textarea, which requires English spelling.
document.getElementById('en-US').focus();
});
}
function enFocus() {
var elem_en = document.getElementById('en-US');
var editor_en = elem_en.QueryInterface(Components.interfaces.nsIDOMNSEditableElement).editor;
editor_en.setSpellcheckUserOverride(true);
var inlineSpellChecker = editor_en.getInlineSpellChecker(true);
onSpellCheck(elem_en, function () {
var spellchecker = inlineSpellChecker.spellChecker;
try {
currentDictonary = spellchecker.GetCurrentDictionary();
} catch(e) {}
// Check that the English dictionary is loaded and that the spell check has worked.
is(currentDictonary, "en-US", "expected en-US");
is(getMisspelledWords(editor_en), "Nogoodword", "one misspelled word expected: Nogoodword");
// So far all was boring. The important thing is whether the spell check result
// in the de-DE editor is still the same. After losing focus, no spell check
// updates should take place there.
var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
is(sel.toString(), "German", "one misspelled word expected: German");
// Remove the fake de_DE dictionary again.
hunspell.removeDirectory(de_DE);
// Focus again, so the spelling gets updated, but before we need to kill the focus handler.
elem_de.onfocus = null;
elem_de.blur();
elem_de.focus();
// After removal, the de_DE editor should refresh the spelling with en-US.
onSpellCheck(elem_de, function () {
var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
is(sel.toString(), "heute" + "ist" + "ein" + "guter",
"some misspelled words expected: heute ist ein guter");
// If we don't reset this, we cause massive leaks.
selcon_de = null;
editor_de = null;
SimpleTest.finish();
});
});
}
</script>
</pre>
</body>
</html>

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

@ -222,6 +222,7 @@ if CONFIG['GNU_CC'] or CONFIG['CLANG_CL']:
'-Wno-type-limits',
'-Wno-missing-field-initializers',
'-Wno-conversion',
'-Wno-unused-but-set-variable',
]
if CONFIG['CLANG_CXX'] or CONFIG['CLANG_CL']:
CFLAGS += [

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

@ -825,6 +825,7 @@ GLContext::InitWithPrefixImpl(const char* prefix, bool trygl)
"Mali-450 MP",
"PowerVR SGX 530",
"PowerVR SGX 540",
"PowerVR SGX 544MP",
"NVIDIA Tegra",
"Android Emulator",
"Gallium 0.4 on llvmpipe",
@ -1801,6 +1802,17 @@ GLContext::InitExtensions()
MarkExtensionUnsupported(OES_EGL_sync);
}
#ifdef MOZ_WIDGET_ANDROID
if (Vendor() == GLVendor::Imagination &&
Renderer() == GLRenderer::SGX544MP &&
AndroidBridge::Bridge()->GetAPIVersion() < 21)
{
// Bug 1026404
MarkExtensionUnsupported(OES_EGL_image);
MarkExtensionUnsupported(OES_EGL_image_external);
}
#endif
if (Vendor() == GLVendor::ARM &&
(Renderer() == GLRenderer::Mali400MP ||
Renderer() == GLRenderer::Mali450MP))

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