зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to a CLOSED TREE m-i
MozReview-Commit-ID: 2JxLeQ8GYIX
This commit is contained in:
Коммит
b6eec64324
|
@ -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
|
|
@ -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))
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче