diff --git a/.eslintignore b/.eslintignore
index 0bcd8ce43861..f12fbc099222 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -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/**
diff --git a/browser/base/content/browser-social.js b/browser/base/content/browser-social.js
index bceb49e6a579..b470efd3d5e7 100644
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -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;
diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js
index 0321d694972b..b1906537128b 100644
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js
@@ -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();
}
diff --git a/browser/components/feeds/FeedConverter.js b/browser/components/feeds/FeedConverter.js
index 5e18c0982e93..9e6fc17e71fe 100644
--- a/browser/components/feeds/FeedConverter.js
+++ b/browser/components/feeds/FeedConverter.js
@@ -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 */ }
}
}
}
diff --git a/browser/components/feeds/FeedWriter.js b/browser/components/feeds/FeedWriter.js
index 0c99f58d7fab..51bf53d5b6a9 100644
--- a/browser/components/feeds/FeedWriter.js
+++ b/browser/components/feeds/FeedWriter.js
@@ -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;
diff --git a/browser/components/feeds/WebContentConverter.js b/browser/components/feeds/WebContentConverter.js
index 0fef8b1d2f13..76fe4e7bf554 100644
--- a/browser/components/feeds/WebContentConverter.js
+++ b/browser/components/feeds/WebContentConverter.js
@@ -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
}
}
diff --git a/browser/components/feeds/content/subscribe.js b/browser/components/feeds/content/subscribe.js
index ab2eac4ebc54..99c6d4e9e89a 100644
--- a/browser/components/feeds/content/subscribe.js
+++ b/browser/components/feeds/content/subscribe.js
@@ -8,7 +8,7 @@ var SubscribeHandler = {
* The nsIFeedWriter object that produces the UI
*/
_feedWriter: null,
-
+
init: function SH_init() {
this._feedWriter = new BrowserFeedWriter();
},
diff --git a/browser/components/feeds/test/test_bug436801.html b/browser/components/feeds/test/test_bug436801.html
index a72d2c11a059..29fb5acf0a69 100644
--- a/browser/components/feeds/test/test_bug436801.html
+++ b/browser/components/feeds/test/test_bug436801.html
@@ -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");
}
diff --git a/browser/components/feeds/test/test_registerHandler.html b/browser/components/feeds/test/test_registerHandler.html
index 5241caf0e5d6..34e61d034f9e 100644
--- a/browser/components/feeds/test/test_registerHandler.html
+++ b/browser/components/feeds/test/test_registerHandler.html
@@ -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");
diff --git a/browser/components/translation/BingTranslator.jsm b/browser/components/translation/BingTranslator.jsm
index bb498f0464ea..6c23935b8464 100644
--- a/browser/components/translation/BingTranslator.jsm
+++ b/browser/components/translation/BingTranslator.jsm
@@ -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;
diff --git a/browser/components/translation/TranslationDocument.jsm b/browser/components/translation/TranslationDocument.jsm
index a47af78af8fe..98bcaf261b6b 100644
--- a/browser/components/translation/TranslationDocument.jsm
+++ b/browser/components/translation/TranslationDocument.jsm
@@ -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 += '
';
- wasLastItemPlaceholder = true;
- }
+ item.original.push(TranslationItem_NodePlaceholder);
+ str += '
';
+ 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 + "]";
},
diff --git a/browser/components/translation/YandexTranslator.jsm b/browser/components/translation/YandexTranslator.jsm
index 6d471a00f354..ab92e09625c9 100644
--- a/browser/components/translation/YandexTranslator.jsm
+++ b/browser/components/translation/YandexTranslator.jsm
@@ -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");
diff --git a/browser/components/translation/cld2/post.js b/browser/components/translation/cld2/post.js
index 99fa4d6084a5..a3e8b8522c03 100644
--- a/browser/components/translation/cld2/post.js
+++ b/browser/components/translation/cld2/post.js
@@ -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;
diff --git a/browser/components/translation/test/browser_translation_bing.js b/browser/components/translation/test/browser_translation_bing.js
index 662df8e4ff3d..399a67022dd1 100644
--- a/browser/components/translation/test/browser_translation_bing.js
+++ b/browser/components/translation/test/browser_translation_bing.js
@@ -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://", "");
diff --git a/browser/components/translation/test/browser_translation_telemetry.js b/browser/components/translation/test/browser_translation_telemetry.js
index 2fdda7459b7b..7d87da12933f 100644
--- a/browser/components/translation/test/browser_translation_telemetry.js
+++ b/browser/components/translation/test/browser_translation_telemetry.js
@@ -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) {
diff --git a/browser/components/translation/test/browser_translation_yandex.js b/browser/components/translation/test/browser_translation_yandex.js
index 4d66126dfb66..6e0af18e6c39 100644
--- a/browser/components/translation/test/browser_translation_yandex.js
+++ b/browser/components/translation/test/browser_translation_yandex.js
@@ -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://", "");
diff --git a/browser/locales/Makefile.in b/browser/locales/Makefile.in
index c8db2bc006d1..5c3b2b2a7973 100644
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -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
diff --git a/devtools/client/animationinspector/components/animation-time-block.js b/devtools/client/animationinspector/components/animation-time-block.js
index 2a68a8344b10..65c9e78aef55 100644
--- a/devtools/client/animationinspector/components/animation-time-block.js
+++ b/devtools/client/animationinspector/components/animation-time-block.js
@@ -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)"
+ }
+ });
+}
diff --git a/devtools/client/animationinspector/test/browser.ini b/devtools/client/animationinspector/test/browser.ini
index 8d6343612f24..f13f382443a1 100644
--- a/devtools/client/animationinspector/test/browser.ini
+++ b/devtools/client/animationinspector/test/browser.ini
@@ -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]
diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_iterationStart.js b/devtools/client/animationinspector/test/browser_animation_timeline_iterationStart.js
index 5e2d847c2e6b..c05f15d27d99 100644
--- a/devtools/client/animationinspector/test/browser_animation_timeline_iterationStart.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_iterationStart.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}) {
diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_setCurrentTime.js b/devtools/client/animationinspector/test/browser_animation_timeline_setCurrentTime.js
new file mode 100644
index 000000000000..efc32c001352
--- /dev/null
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_setCurrentTime.js
@@ -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 }`);
+ }
+});
diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_shows_delay.js b/devtools/client/animationinspector/test/browser_animation_timeline_shows_delay.js
index 8082eb0cf9ce..8c9b0653dbf2 100644
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_delay.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_delay.js
@@ -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 }`);
}
}
diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js b/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js
index 2c9f60746e2d..0aa5c16c0777 100644
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js
@@ -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 }`);
+ }
+}
diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js b/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js
index dd6b4e5b3d6f..08e5a2620e4b 100644
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js
@@ -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;
}
diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js b/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js
index 80eda566c7f4..f330e880e18a 100644
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js
@@ -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");
});
diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js b/devtools/client/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js
index 99efea9324c6..42309203abf5 100644
--- a/devtools/client/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js
@@ -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;
+}
diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_ui.js b/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
index f88275b3ef60..43c148482b89 100644
--- a/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
@@ -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");
}
});
diff --git a/devtools/client/animationinspector/test/doc_timing_combination_animation.html b/devtools/client/animationinspector/test/doc_timing_combination_animation.html
new file mode 100644
index 000000000000..8b39af015cdb
--- /dev/null
+++ b/devtools/client/animationinspector/test/doc_timing_combination_animation.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/devtools/client/animationinspector/utils.js b/devtools/client/animationinspector/utils.js
index 9619fb59036a..45ebab00d2a3 100644
--- a/devtools/client/animationinspector/utils.js
+++ b/devtools/client/animationinspector/utils.js
@@ -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;
}
};
diff --git a/devtools/client/debugger/content/views/sources-view.js b/devtools/client/debugger/content/views/sources-view.js
index 81610b560d3e..bb68afcf49d2 100644
--- a/devtools/client/debugger/content/views/sources-view.js
+++ b/devtools/client/debugger/content/views/sources-view.js
@@ -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";
diff --git a/devtools/client/debugger/debugger-controller.js b/devtools/client/debugger/debugger-controller.js
index 386da446586d..f4e0546b7ef7 100644
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -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"]
});
/**
diff --git a/devtools/client/debugger/debugger-view.js b/devtools/client/debugger/debugger-view.js
index 964065367777..b6a5850ff8b0 100644
--- a/devtools/client/debugger/debugger-view.js
+++ b/devtools/client/debugger/debugger-view.js
@@ -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.
*
diff --git a/devtools/client/debugger/debugger.xul b/devtools/client/debugger/debugger.xul
index 19c1534f13e5..5a22cf7f8ea4 100644
--- a/devtools/client/debugger/debugger.xul
+++ b/devtools/client/debugger/debugger.xul
@@ -341,11 +341,6 @@
class="devtools-toolbarbutton"
tooltiptext="&debuggerUI.sources.toggleBreakpoints;"
command="toggleBreakpointsCommand"/>
-
@@ -403,12 +398,6 @@
-
-
-
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js b/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js
index dc86d8649370..82a23cada7db 100644
--- a/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js
@@ -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.");
diff --git a/devtools/client/framework/test/browser_toolbox_select_event.js b/devtools/client/framework/test/browser_toolbox_select_event.js
index 346e55986821..ae104524e879 100644
--- a/devtools/client/framework/test/browser_toolbox_select_event.js
+++ b/devtools/client/framework/test/browser_toolbox_select_event.js
@@ -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();
}
});
diff --git a/devtools/client/framework/toolbox.js b/devtools/client/framework/toolbox.js
index fa148d45ccf8..845bcc357390 100644
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -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) {
diff --git a/devtools/client/inspector/test/browser.ini b/devtools/client/inspector/test/browser.ini
index 5d4658187431..65ad71c0c00b 100644
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -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]
diff --git a/devtools/client/inspector/test/browser_inspector_destroy-after-navigation.js b/devtools/client/inspector/test/browser_inspector_destroy-after-navigation.js
index e89fdd3c8656..5fcd5538b6de 100644
--- a/devtools/client/inspector/test/browser_inspector_destroy-after-navigation.js
+++ b/devtools/client/inspector/test/browser_inspector_destroy-after-navigation.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 {
diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-xul.js b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-xul.js
index 9ab77d517075..7c44e72757db 100644
--- a/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-xul.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-xul.js
@@ -11,7 +11,7 @@ const TEST_URL_2 =
"data:text/html;charset=utf-8,HTML test page
";
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");
diff --git a/devtools/client/inspector/test/browser_inspector_navigate_to_errors.js b/devtools/client/inspector/test/browser_inspector_navigate_to_errors.js
new file mode 100644
index 000000000000..c2266c852f9e
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_navigate_to_errors.js
@@ -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,page";
+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,test-doc-4";
+
+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");
+});
diff --git a/devtools/client/inspector/test/browser_inspector_navigation.js b/devtools/client/inspector/test/browser_inspector_navigation.js
index 2e19ad66f60a..dab6f7007293 100644
--- a/devtools/client/inspector/test/browser_inspector_navigation.js
+++ b/devtools/client/inspector/test/browser_inspector_navigation.js
@@ -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.");
diff --git a/devtools/client/inspector/test/browser_inspector_open_on_neterror.js b/devtools/client/inspector/test/browser_inspector_open_on_neterror.js
new file mode 100644
index 000000000000..01e065a1a7b4
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_open_on_neterror.js
@@ -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,test-doc-2";
+
+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");
+});
diff --git a/devtools/client/inspector/test/browser_inspector_select-last-selected.js b/devtools/client/inspector/test/browser_inspector_select-last-selected.js
index 31eaad4d519b..0f2050327a52 100644
--- a/devtools/client/inspector/test/browser_inspector_select-last-selected.js
+++ b/devtools/client/inspector/test/browser_inspector_select-last-selected.js
@@ -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);
+ }
}
});
diff --git a/devtools/client/inspector/test/head.js b/devtools/client/inspector/test/head.js
index 5ce6a89518b8..173022f18417 100644
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -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;
+}
diff --git a/devtools/client/jar.mn b/devtools/client/jar.mn
index da5d32cd3626..57df8eedecec 100644
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -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)
diff --git a/devtools/client/locales/en-US/animationinspector.properties b/devtools/client/locales/en-US/animationinspector.properties
index c126095697ea..a61f07f57c8a 100644
--- a/devtools/client/locales/en-US/animationinspector.properties
+++ b/devtools/client/locales/en-US/animationinspector.properties
@@ -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
diff --git a/devtools/client/locales/en-US/debugger.dtd b/devtools/client/locales/en-US/debugger.dtd
index 2189f61c05ef..f2cc46cda53f 100644
--- a/devtools/client/locales/en-US/debugger.dtd
+++ b/devtools/client/locales/en-US/debugger.dtd
@@ -50,10 +50,6 @@
- button that toggles all breakpoints for all sources. -->
-
-
-
diff --git a/devtools/client/locales/en-US/promisedebugger.dtd b/devtools/client/locales/en-US/promisedebugger.dtd
deleted file mode 100644
index eeef89645b9e..000000000000
--- a/devtools/client/locales/en-US/promisedebugger.dtd
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/devtools/client/moz.build b/devtools/client/moz.build
index 1da1475aa2f1..b63de757c231 100644
--- a/devtools/client/moz.build
+++ b/devtools/client/moz.build
@@ -22,7 +22,6 @@ DIRS += [
'performance',
'preferences',
'projecteditor',
- 'promisedebugger',
'responsive.html',
'responsivedesign',
'scratchpad',
diff --git a/devtools/client/preferences/devtools.js b/devtools/client/preferences/devtools.js
index 5f80b746a846..ebf524694b8b 100644
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -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);
diff --git a/devtools/client/promisedebugger/moz.build b/devtools/client/promisedebugger/moz.build
deleted file mode 100644
index fefe5ba48bbe..000000000000
--- a/devtools/client/promisedebugger/moz.build
+++ /dev/null
@@ -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(
-)
diff --git a/devtools/client/promisedebugger/promise-controller.js b/devtools/client/promisedebugger/promise-controller.js
deleted file mode 100644
index e5ca4aafcbe7..000000000000
--- a/devtools/client/promisedebugger/promise-controller.js
+++ /dev/null
@@ -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);
diff --git a/devtools/client/promisedebugger/promise-debugger.xhtml b/devtools/client/promisedebugger/promise-debugger.xhtml
deleted file mode 100644
index 4394c877553f..000000000000
--- a/devtools/client/promisedebugger/promise-debugger.xhtml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
- %promisedebuggerDTD;
-]>
-
-
-
- &title;
-
-
-
-
-
-
-
-
diff --git a/devtools/client/promisedebugger/promise-panel.js b/devtools/client/promisedebugger/promise-panel.js
deleted file mode 100644
index f0a4025de59b..000000000000
--- a/devtools/client/promisedebugger/promise-panel.js
+++ /dev/null
@@ -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);
diff --git a/devtools/client/promisedebugger/test/.eslintrc.js b/devtools/client/promisedebugger/test/.eslintrc.js
deleted file mode 100644
index 8d15a76d9b8c..000000000000
--- a/devtools/client/promisedebugger/test/.eslintrc.js
+++ /dev/null
@@ -1,6 +0,0 @@
-"use strict";
-
-module.exports = {
- // Extend from the shared list of defined globals for mochitests.
- "extends": "../../../.eslintrc.mochitests.js"
-};
diff --git a/devtools/client/promisedebugger/test/head.js b/devtools/client/promisedebugger/test/head.js
deleted file mode 100644
index 85d66c5323f6..000000000000
--- a/devtools/client/promisedebugger/test/head.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
diff --git a/devtools/client/responsive.html/browser/tunnel.js b/devtools/client/responsive.html/browser/tunnel.js
index aeff3d552448..db85a36e7230 100644
--- a/devtools/client/responsive.html/browser/tunnel.js
+++ b/devtools/client/responsive.html/browser/tunnel.js
@@ -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 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: [
diff --git a/devtools/client/themes/animationinspector.css b/devtools/client/themes/animationinspector.css
index 4b8e5d9c85f8..dcac62cbfc95 100644
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -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 */
diff --git a/devtools/client/themes/debugger.css b/devtools/client/themes/debugger.css
index 8e6eaf568417..3f2d49a0fa05 100644
--- a/devtools/client/themes/debugger.css
+++ b/devtools/client/themes/debugger.css
@@ -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);
}
diff --git a/devtools/server/actors/animation.js b/devtools/server/actors/animation.js
index 4be8e71310a3..642c4bcafba6 100644
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -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;
},
diff --git a/devtools/server/actors/highlighters.js b/devtools/server/actors/highlighters.js
index a33a7d82a51c..087750ab30a1 100644
--- a/devtools/server/actors/highlighters.js
+++ b/devtools/server/actors/highlighters.js
@@ -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();
+ }
},
/**
diff --git a/devtools/server/actors/highlighters/utils/markup.js b/devtools/server/actors/highlighters/utils/markup.js
index 7ea8b0e46de3..8750014bc01b 100644
--- a/devtools/server/actors/highlighters/utils/markup.js
+++ b/devtools/server/actors/highlighters/utils/markup.js
@@ -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);
diff --git a/devtools/server/actors/webbrowser.js b/devtools/server/actors/webbrowser.js
index 93a7c6dcc976..0edcdc18756e 100644
--- a/devtools/server/actors/webbrowser.js
+++ b/devtools/server/actors/webbrowser.js
@@ -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")
};
diff --git a/devtools/server/tests/browser/animation.html b/devtools/server/tests/browser/animation.html
index 25a368641a67..d10a9873dda4 100644
--- a/devtools/server/tests/browser/animation.html
+++ b/devtools/server/tests/browser/animation.html
@@ -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;
diff --git a/devtools/server/tests/browser/browser_animation_playerState.js b/devtools/server/tests/browser/browser_animation_playerState.js
index c48f04334812..ac5842e39c9e 100644
--- a/devtools/server/tests/browser/browser_animation_playerState.js
+++ b/devtools/server/tests/browser/browser_animation_playerState.js
@@ -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;
}
diff --git a/devtools/shared/fronts/animation.js b/devtools/shared/fronts/animation.js
index 4768feabefd8..01c9f0bfab95 100644
--- a/devtools/shared/fronts/animation.js
+++ b/devtools/shared/fronts/animation.js
@@ -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
diff --git a/docshell/shistory/moz.build b/docshell/shistory/moz.build
index 2cfd875ae202..c85c3a3cbdf4 100644
--- a/docshell/shistory/moz.build
+++ b/docshell/shistory/moz.build
@@ -6,6 +6,9 @@
XPIDL_SOURCES += [
'nsIBFCacheEntry.idl',
+ 'nsIGroupedSHistory.idl',
+ 'nsIPartialSHistory.idl',
+ 'nsIPartialSHistoryListener.idl',
'nsISHContainer.idl',
'nsISHEntry.idl',
'nsISHistory.idl',
diff --git a/docshell/shistory/nsIGroupedSHistory.idl b/docshell/shistory/nsIGroupedSHistory.idl
new file mode 100644
index 000000000000..4001dcc34b4b
--- /dev/null
+++ b/docshell/shistory/nsIGroupedSHistory.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);
+};
diff --git a/docshell/shistory/nsIPartialSHistory.idl b/docshell/shistory/nsIPartialSHistory.idl
new file mode 100644
index 000000000000..99359794e765
--- /dev/null
+++ b/docshell/shistory/nsIPartialSHistory.idl
@@ -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();
+};
diff --git a/docshell/shistory/nsIPartialSHistoryListener.idl b/docshell/shistory/nsIPartialSHistoryListener.idl
new file mode 100644
index 000000000000..f0cdfd7e4d44
--- /dev/null
+++ b/docshell/shistory/nsIPartialSHistoryListener.idl
@@ -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);
+};
diff --git a/docshell/shistory/nsISHistory.idl b/docshell/shistory/nsISHistory.idl
index 372c00858920..71da2e1ab036 100644
--- a/docshell/shistory/nsISHistory.idl
+++ b/docshell/shistory/nsISHistory.idl
@@ -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);
};
diff --git a/docshell/shistory/nsISHistoryListener.idl b/docshell/shistory/nsISHistoryListener.idl
index 38d657575757..38552d60e96b 100644
--- a/docshell/shistory/nsISHistoryListener.idl
+++ b/docshell/shistory/nsISHistoryListener.idl
@@ -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);
};
diff --git a/docshell/shistory/nsSHistory.cpp b/docshell/shistory/nsSHistory.cpp
index c76cf7d150f7..7c148ffcc749 100644
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -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(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 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(mRootDocShell),
+ &nsDocShell::FireDummyOnLocationChange));
+ return NS_OK;
+
+}
+
nsSHEnumerator::nsSHEnumerator(nsSHistory* aSHistory) : mIndex(-1)
{
mSHistory = aSHistory;
diff --git a/docshell/shistory/nsSHistory.h b/docshell/shistory/nsSHistory.h
index 3deece4f64b2..cddc272695f8 100644
--- a/docshell/shistory/nsSHistory.h
+++ b/docshell/shistory/nsSHistory.h
@@ -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 mListeners;
+
+ // Partial session history listener
+ nsWeakPtr mPartialHistoryListener;
+
// Weak reference. Do not refcount this.
nsIDocShell* mRootDocShell;
diff --git a/dom/base/GroupedSHistory.cpp b/dom/base/GroupedSHistory.cpp
new file mode 100644
index 000000000000..266b1fd36d46
--- /dev/null
+++ b/dom/base/GroupedSHistory.cpp
@@ -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 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 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 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 currentPartialHistory =
+ mPartialHistories[mIndexOfActivePartialHistory];
+ if (!currentPartialHistory) {
+ // Cycle collected?
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ for (uint32_t i = 0; i < mPartialHistories.Length(); i++) {
+ nsCOMPtr 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 partialHistory = mPartialHistories[i];
+ if (!partialHistory) {
+ // Cycle collected?
+ return;
+ }
+
+ nsCOMPtr 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
diff --git a/dom/base/GroupedSHistory.h b/dom/base/GroupedSHistory.h
new file mode 100644
index 000000000000..8c88a0aafec0
--- /dev/null
+++ b/dom/base/GroupedSHistory.h
@@ -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 mPartialHistories;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* GroupedSHistory_h */
diff --git a/dom/base/PartialSHistory.cpp b/dom/base/PartialSHistory.cpp
new file mode 100644
index 000000000000..930d563b8d97
--- /dev/null
+++ b/dom/base/PartialSHistory.cpp
@@ -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
+PartialSHistory::GetSessionHistory()
+{
+ if (!mOwnerFrameLoader) {
+ // Cycle collected?
+ return nullptr;
+ }
+
+ nsCOMPtr docShell;
+ mOwnerFrameLoader->GetDocShell(getter_AddRefs(docShell));
+ if (!docShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr webNav(do_QueryInterface(docShell));
+ nsCOMPtr shistory;
+ webNav->GetSessionHistory(getter_AddRefs(shistory));
+ return shistory.forget();
+}
+
+already_AddRefed
+PartialSHistory::GetTabParent()
+{
+ if (!mOwnerFrameLoader) {
+ // Cycle collected?
+ return nullptr;
+ }
+
+ nsCOMPtr tabParent;
+ mOwnerFrameLoader->GetTabParent(getter_AddRefs(tabParent));
+ return RefPtr(static_cast(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 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 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 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 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(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 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(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 shistory(GetSessionHistory());
+ if (shistory) {
+ if (NS_FAILED(shistory->OnPartialSessionHistoryDeactive())) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+ }
+
+ // Cross-process case.
+ RefPtr 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 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
diff --git a/dom/base/PartialSHistory.h b/dom/base/PartialSHistory.h
new file mode 100644
index 000000000000..5e8a435e67f7
--- /dev/null
+++ b/dom/base/PartialSHistory.h
@@ -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 GetSessionHistory();
+ already_AddRefed 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 mOwnerFrameLoader;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* PartialSHistory_h */
diff --git a/dom/base/moz.build b/dom/base/moz.build
index 2115614c7ccc..8ca927001d42 100644
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -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',
diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp
index c5a657b465db..d578e019f8a2 100644
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.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 partialHistory(mPartialSessionHistory);
+ partialHistory.forget(aResult);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsFrameLoader::GetGroupedSessionHistory(nsIGroupedSHistory** aResult)
+{
+ nsCOMPtr groupedHistory(mGroupedSessionHistory);
+ groupedHistory.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::AppendPartialSessionHistoryAndSwap(nsIFrameLoader* aOther)
+{
+ if (!aOther) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ nsCOMPtr 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 otherLoader = static_cast(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 ourBrowser = do_QueryInterface(mOwnerContent);
+ nsCOMPtr 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 targetLoader;
+ nsresult rv = mGroupedSessionHistory->
+ GotoIndex(aGlobalIndex, getter_AddRefs(targetLoader));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr otherLoader = static_cast(targetLoader.get());
+ if (!targetLoader) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (targetLoader == this) {
+ return NS_OK;
+ }
+
+ nsCOMPtr ourBrowser = do_QueryInterface(mOwnerContent);
+ nsCOMPtr 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 webNav(do_QueryInterface(mDocShell));
webNav->SetSessionHistory(sessionHistory);
+
+
+ if (GroupedSHistory::GroupedHistoryEnabled()) {
+ mPartialSessionHistory = new PartialSHistory(this);
+ nsCOMPtr listener(do_QueryInterface(mPartialSessionHistory));
+ nsCOMPtr 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 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,
diff --git a/dom/base/nsFrameLoader.h b/dom/base/nsFrameLoader.h
index 0c915fc5eff9..d374bbae8b4a 100644
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -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 mPartialSessionHistory;
+ nsCOMPtr mGroupedSessionHistory;
+
bool mIsPrerendered : 1;
bool mDepthTooGreat : 1;
bool mIsTopLevelContent : 1;
diff --git a/dom/base/nsIFrameLoader.idl b/dom/base/nsIFrameLoader.idl
index 08cf16fffbee..6a8ae284d10c 100644
--- a/dom/base/nsIFrameLoader.idl
+++ b/dom/base/nsIFrameLoader.idl
@@ -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++
diff --git a/dom/base/test/chrome/chrome.ini b/dom/base/test/chrome/chrome.ini
index a1ed9ec24fae..e9d8f61ae4b4 100644
--- a/dom/base/test/chrome/chrome.ini
+++ b/dom/base/test/chrome/chrome.ini
@@ -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]
diff --git a/dom/base/test/chrome/test_groupedSHistory.xul b/dom/base/test/chrome/test_groupedSHistory.xul
new file mode 100644
index 000000000000..23cd4c6adbce
--- /dev/null
+++ b/dom/base/test/chrome/test_groupedSHistory.xul
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+ Mozilla Bug 1276553
+
+
+
+
+
diff --git a/dom/base/test/chrome/window_groupedSHistory.xul b/dom/base/test/chrome/window_groupedSHistory.xul
new file mode 100644
index 000000000000..e509fcc5b096
--- /dev/null
+++ b/dom/base/test/chrome/window_groupedSHistory.xul
@@ -0,0 +1,331 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/dom/interfaces/base/moz.build b/dom/interfaces/base/moz.build
index 3aac837091e6..e96fc0158ee0 100644
--- a/dom/interfaces/base/moz.build
+++ b/dom/interfaces/base/moz.build
@@ -6,6 +6,7 @@
XPIDL_SOURCES += [
'domstubs.idl',
+ 'nsIBrowser.idl',
'nsIBrowserDOMWindow.idl',
'nsIContentPermissionPrompt.idl',
'nsIContentPrefService.idl',
diff --git a/dom/ipc/nsIBrowser.idl b/dom/interfaces/base/nsIBrowser.idl
similarity index 53%
rename from dom/ipc/nsIBrowser.idl
rename to dom/interfaces/base/nsIBrowser.idl
index 6bdac8afa0cb..44ae9b0a2684 100644
--- a/dom/ipc/nsIBrowser.idl
+++ b/dom/interfaces/base/nsIBrowser.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 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();
};
diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl
index 5fb0935c688c..970f6f523a53 100644
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -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!
diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp
index 171f33da139b..4c48d2bf10a9 100644
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -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 shistory;
+ mWebNav->GetSessionHistory(getter_AddRefs(shistory));
+ if (!shistory) {
+ return NS_ERROR_FAILURE;
+ }
+ mHistoryListener = new TabChildSHistoryListener(this);
+ nsCOMPtr listener(do_QueryObject(mHistoryListener));
+ shistory->AddSHistoryListener(listener);
+ nsCOMPtr partialListener(do_QueryObject(mHistoryListener));
+ shistory->SetPartialSHistoryListener(partialListener);
+ }
+
return NS_OK;
}
@@ -1292,12 +1313,16 @@ TabChild::ActorDestroy(ActorDestroyReason why)
TabChild::~TabChild()
{
- DestroyWindow();
+ DestroyWindow();
- nsCOMPtr webBrowser = do_QueryInterface(WebNavigation());
- if (webBrowser) {
- webBrowser->SetContainerWindow(nullptr);
- }
+ nsCOMPtr 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 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 shistory;
+ mWebNav->GetSessionHistory(getter_AddRefs(shistory));
+ NS_ENSURE_TRUE(shistory, false);
+
+ return NS_SUCCEEDED(shistory->OnPartialSessionHistoryActive(aGlobalLength,
+ aTargetLocalIndex));
+}
+
+bool
+TabChild::RecvNotifyPartialSessionHistoryDeactive()
+{
+ nsCOMPtr 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(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(mTabChild);
+ if (!tabChild) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return tabChild->SendRequestCrossBrowserNavigation(aIndex) ?
+ NS_OK : NS_ERROR_FAILURE;
+}
+
TabChildGlobal::TabChildGlobal(TabChildBase* aTabChild)
: mTabChild(aTabChild)
{
diff --git a/dom/ipc/TabChild.h b/dom/ipc/TabChild.h
index 17dc449531d3..85c89a8b96bb 100644
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -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 mLastURI;
RenderFrameChild* mRemoteFrame;
RefPtr mManager;
+ RefPtr mHistoryListener;
uint32_t mChromeFlags;
int32_t mActiveSuppressDisplayport;
uint64_t mLayersId;
diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp
index 2760d2a8722b..e58e12570005 100644
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -100,6 +100,8 @@
#include "UnitTransforms.h"
#include
#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 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 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 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)
{
diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h
index 99f497867f0e..b5d2cb5f36dc 100644
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -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;
diff --git a/dom/ipc/moz.build b/dom/ipc/moz.build
index ca24a1485f2c..0f4eb7d004e9 100644
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -5,7 +5,6 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
XPIDL_SOURCES += [
- 'nsIBrowser.idl',
'nsIHangReport.idl',
]
diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp
index 3dc9783941e2..b98055c6bd5f 100644
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -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()
diff --git a/dom/media/PeerConnection.js b/dom/media/PeerConnection.js
index aae7b3ea4323..98b8debbeb94 100644
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -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");
}
diff --git a/dom/media/eme/MediaKeySystemAccess.cpp b/dom/media/eme/MediaKeySystemAccess.cpp
index 6f44f4c9b471..c9aab55d4506 100644
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -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 mCodecsDecoded;
- nsTArray mCodecsDecrypted;
+ nsTArray mCodecsDecoded;
+ nsTArray mCodecsDecrypted;
};
enum class KeySystemFeatureSupport
@@ -414,16 +426,33 @@ struct KeySystemConfig
KeySystemContainerSupport mWebM;
};
-StaticAutoPtr> sKeySystemConfigs;
+bool
+HavePluginForKeySystem(const nsCString& aKeySystem)
+{
+ bool havePlugin = false;
+ nsCOMPtr 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&
+static nsTArray
GetSupportedKeySystems()
{
- if (!sKeySystemConfigs) {
- sKeySystemConfigs = new nsTArray();
- ClearOnShutdown(&sKeySystemConfigs);
+ nsTArray 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& aCodecs,
+ const nsTArray& 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& aCodecs, const CodecType aCodecType)
+AllCodecsOfType(const nsTArray& 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
GetSupportedCapabilities(const CodecType aCodecType,
- mozIGeckoMediaPluginService* aGMPService,
const nsTArray& aRequestedCapabilities,
const MediaKeySystemConfiguration& aPartialConfig,
const KeySystemConfig& aKeySystem,
@@ -731,10 +794,10 @@ GetSupportedCapabilities(const CodecType aCodecType,
continue;
}
bool invalid = false;
- nsTArray codecs;
+ nsTArray 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>& 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 caps =
GetSupportedCapabilities(Video,
- aGMPService,
aCandidate.mVideoCapabilities,
config,
aKeySystem,
@@ -1121,7 +1181,6 @@ GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
// member, accumulated configuration, and restrictions.
Sequence caps =
GetSupportedCapabilities(Audio,
- aGMPService,
aCandidate.mAudioCapabilities,
config,
aKeySystem,
@@ -1204,21 +1263,12 @@ MediaKeySystemAccess::GetSupportedConfig(const nsAString& aKeySystem,
MediaKeySystemConfiguration& aOutConfig,
DecoderDoctorDiagnostics* aDiagnostics)
{
- nsCOMPtr 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)) {
diff --git a/dom/media/tests/mochitest/test_peerConnection_bug825703.html b/dom/media/tests/mochitest/test_peerConnection_bug825703.html
index 7a1c06a1384f..c25079753ed7 100644
--- a/dom/media/tests/mochitest/test_peerConnection_bug825703.html
+++ b/dom/media/tests/mochitest/test_peerConnection_bug825703.html
@@ -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" },
diff --git a/editor/composer/test/test_bug1205983.html b/editor/composer/test/test_bug1205983.html
index 5ed9cdfb56b7..da9eaef0e3fe 100644
--- a/editor/composer/test/test_bug1205983.html
+++ b/editor/composer/test/test_bug1205983.html
@@ -1,126 +1,126 @@
-
-
-
-
- Test for Bug 1205983
-
-
-
-
-Mozilla Bug 1205983
-
-
-
-German heute ist ein guter Tag
-
-
-
-
-
-
-
+
+
+
+
+ Test for Bug 1205983
+
+
+
+
+Mozilla Bug 1205983
+
+
+
+German heute ist ein guter Tag
+
+
+
+
+
+
+
diff --git a/gfx/cairo/cairo/src/moz.build b/gfx/cairo/cairo/src/moz.build
index f92406e38169..14b602ac2399 100644
--- a/gfx/cairo/cairo/src/moz.build
+++ b/gfx/cairo/cairo/src/moz.build
@@ -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 += [
diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp
index 41febc1eb735..ddfd7b0c314c 100644
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -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))
diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h
index 0f3c26588dc8..9b80eb2a9b83 100644
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -180,6 +180,7 @@ enum class GLRenderer {
Mali450MP,
SGX530,
SGX540,
+ SGX544MP,
Tegra,
AndroidEmulator,
GalliumLlvmpipe,
diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp
index 0cad8d019470..aa4c6dcfd507 100644
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -2382,6 +2382,10 @@ CanOptimizeForDenseStorage(HandleObject arr, uint32_t startingIndex, uint32_t co
if (!arr->is() && !arr->is())
return false;
+ /* If it's a frozen array, always pick the slow path */
+ if (arr->is() && arr->as().denseElementsAreFrozen())
+ return false;
+
/*
* Don't optimize if the array might be in the midst of iteration. We
* rely on this to be able to safely move dense array elements around with
diff --git a/js/src/tests/ecma_5/Array/frozen-dense-array.js b/js/src/tests/ecma_5/Array/frozen-dense-array.js
index 783e1a710f20..9db63036f370 100644
--- a/js/src/tests/ecma_5/Array/frozen-dense-array.js
+++ b/js/src/tests/ecma_5/Array/frozen-dense-array.js
@@ -9,11 +9,13 @@ var summary = "Dense array properties shouldn't be modified when they're frozen"
print(BUGNUMBER + ": " + summary);
var a = Object.freeze([4, 5, 1]);
-var methods = [
- 'reverse',
- 'shift',
- 'pop',
-];
+
+function assertArrayIsExpected() {
+ assertEq(a.length, 3);
+ assertEq(a[0], 4);
+ assertEq(a[1], 5);
+ assertEq(a[2], 1);
+}
assertThrowsInstanceOf(() => a.reverse(), TypeError);
assertThrowsInstanceOf(() => a.shift(), TypeError);
@@ -24,18 +26,36 @@ assertThrowsInstanceOf(() => a.fill(0), TypeError);
assertThrowsInstanceOf(() => a.splice(0, 1, 1), TypeError);
assertThrowsInstanceOf(() => a.push("foo"), TypeError);
assertThrowsInstanceOf(() => { "use strict"; a.length = 5; }, TypeError);
+assertThrowsInstanceOf(() => { "use strict"; a[2] = "foo"; }, TypeError);
assertThrowsInstanceOf(() => { "use strict"; delete a[0]; }, TypeError);
+assertThrowsInstanceOf(() => a.splice(Math.a), TypeError);
// Shouldn't throw, since this is not strict mode, but shouldn't change the
// value of the property.
a.length = 5;
+a[2] = "foo";
assertEq(delete a[0], false);
+assertArrayIsExpected();
-assertEq(a.length, 3);
-assertEq(a[0], 4);
-assertEq(a[1], 5);
-assertEq(a[2], 1);
+var watchpointCalled = false;
+// NOTE: Be careful with the position of this test, since this sparsifies the
+// elements and you might not test what you think you're testing otherwise.
+a.watch(2, function(prop, oldValue, newValue) {
+ watchpointCalled = true;
+ assertEq(prop, 2);
+ assertEq(oldValue, 1);
+ assertEq(newValue, "foo");
+});
+
+assertArrayIsExpected();
+
+a.length = 5;
+a[2] = "foo";
+assertEq(watchpointCalled, true);
+assertEq(delete a[0], false);
+
+assertArrayIsExpected();
if (typeof reportCompare === "function")
reportCompare(true, true);
diff --git a/js/src/vm/NativeObject-inl.h b/js/src/vm/NativeObject-inl.h
index 1d9fc76bbfa7..48a42a8db83b 100644
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -99,7 +99,7 @@ NativeObject::removeDenseElementForSparseIndex(ExclusiveContext* cx,
{
MarkObjectGroupFlags(cx, obj, OBJECT_FLAG_NON_PACKED | OBJECT_FLAG_SPARSE_INDEXES);
if (obj->containsDenseElement(index))
- obj->setDenseElement(index, MagicValue(JS_ELEMENTS_HOLE));
+ obj->setDenseElementUnchecked(index, MagicValue(JS_ELEMENTS_HOLE));
}
inline bool
diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp
index 669f019da5b1..0afb77f3dec5 100644
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -519,8 +519,19 @@ NativeObject::sparsifyDenseElement(ExclusiveContext* cx, HandleNativeObject obj,
removeDenseElementForSparseIndex(cx, obj, index);
uint32_t slot = obj->slotSpan();
- if (!obj->addDataProperty(cx, INT_TO_JSID(index), slot, JSPROP_ENUMERATE)) {
- obj->setDenseElement(index, value);
+
+ RootedId id(cx, INT_TO_JSID(index));
+
+ ShapeTable::Entry* entry = nullptr;
+ if (obj->inDictionaryMode())
+ entry = &obj->lastProperty()->table().search(id);
+
+ // NOTE: We don't use addDataProperty because we don't want the
+ // extensibility check if we're, for example, sparsifying frozen objects..
+ if (!addPropertyInternal(cx, obj, id, nullptr, nullptr, slot,
+ obj->getElementsHeader()->elementAttributes(),
+ 0, entry, true)) {
+ obj->setDenseElementUnchecked(index, value);
return false;
}
@@ -548,7 +559,7 @@ NativeObject::sparsifyDenseElements(js::ExclusiveContext* cx, HandleNativeObject
}
if (initialized)
- obj->setDenseInitializedLength(0);
+ obj->setDenseInitializedLengthUnchecked(0);
/*
* Reduce storage for dense elements which are now holes. Explicitly mark
@@ -1008,7 +1019,7 @@ NativeObject::addDataProperty(ExclusiveContext* cx, jsid idArg, uint32_t slot, u
Shape*
NativeObject::addDataProperty(ExclusiveContext* cx, HandlePropertyName name,
- uint32_t slot, unsigned attrs)
+ uint32_t slot, unsigned attrs)
{
MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
RootedNativeObject self(cx, this);
diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h
index 3eb816bd850b..b5f3fd8346a3 100644
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -305,6 +305,12 @@ class ObjectElements
flags &= ~FROZEN;
}
+ uint8_t elementAttributes() const {
+ if (isFrozen())
+ return JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY;
+ return JSPROP_ENUMERATE;
+ }
+
// This is enough slots to store an object of this class. See the static
// assertion below.
static const size_t VALUES_PER_HEADER = 2;
@@ -864,7 +870,14 @@ class NativeObject : public ShapedObject
protected:
inline bool updateSlotsForSpan(ExclusiveContext* cx, size_t oldSpan, size_t newSpan);
- public:
+ private:
+ void prepareElementRangeForOverwrite(size_t start, size_t end) {
+ MOZ_ASSERT(end <= getDenseInitializedLength());
+ MOZ_ASSERT(!denseElementsAreCopyOnWrite());
+ for (size_t i = start; i < end; i++)
+ elements_[i].HeapSlot::~HeapSlot();
+ }
+
/*
* Trigger the write barrier on a range of slots that will no longer be
* reachable.
@@ -874,14 +887,7 @@ class NativeObject : public ShapedObject
getSlotAddressUnchecked(i)->HeapSlot::~HeapSlot();
}
- void prepareElementRangeForOverwrite(size_t start, size_t end) {
- MOZ_ASSERT(end <= getDenseInitializedLength());
- MOZ_ASSERT(!denseElementsAreCopyOnWrite());
- MOZ_ASSERT(!denseElementsAreFrozen());
- for (size_t i = start; i < end; i++)
- elements_[i].HeapSlot::~HeapSlot();
- }
-
+ public:
static bool rollbackProperties(ExclusiveContext* cx, HandleNativeObject obj,
uint32_t slotSpan);
@@ -1015,24 +1021,37 @@ class NativeObject : public ShapedObject
}
}
- public:
- void setDenseInitializedLength(uint32_t length) {
+ // See the comment over setDenseElementUnchecked, this applies in the same way.
+ void setDenseInitializedLengthUnchecked(uint32_t length) {
MOZ_ASSERT(length <= getDenseCapacity());
MOZ_ASSERT(!denseElementsAreCopyOnWrite());
- MOZ_ASSERT(!denseElementsAreFrozen());
prepareElementRangeForOverwrite(length, getElementsHeader()->initializedLength);
getElementsHeader()->initializedLength = length;
}
- inline void ensureDenseInitializedLength(ExclusiveContext* cx,
- uint32_t index, uint32_t extra);
- void setDenseElement(uint32_t index, const Value& val) {
+ // Use this function with care. This is done to allow sparsifying frozen
+ // objects, but should only be called in a few places, and should be
+ // audited carefully!
+ void setDenseElementUnchecked(uint32_t index, const Value& val) {
MOZ_ASSERT(index < getDenseInitializedLength());
MOZ_ASSERT(!denseElementsAreCopyOnWrite());
- MOZ_ASSERT(!denseElementsAreFrozen());
elements_[index].set(this, HeapSlot::Element, index, val);
}
+ public:
+ void setDenseInitializedLength(uint32_t length) {
+ MOZ_ASSERT(!denseElementsAreFrozen());
+ setDenseInitializedLengthUnchecked(length);
+ }
+
+ inline void ensureDenseInitializedLength(ExclusiveContext* cx,
+ uint32_t index, uint32_t extra);
+
+ void setDenseElement(uint32_t index, const Value& val) {
+ MOZ_ASSERT(!denseElementsAreFrozen());
+ setDenseElementUnchecked(index, val);
+ }
+
void initDenseElement(uint32_t index, const Value& val) {
MOZ_ASSERT(index < getDenseInitializedLength());
MOZ_ASSERT(!denseElementsAreCopyOnWrite());
diff --git a/js/src/vm/Shape-inl.h b/js/src/vm/Shape-inl.h
index d8b7d9f0aad0..fa12086a0061 100644
--- a/js/src/vm/Shape-inl.h
+++ b/js/src/vm/Shape-inl.h
@@ -175,9 +175,7 @@ GetShapeAttributes(JSObject* obj, Shape* shape)
if (IsImplicitDenseOrTypedArrayElement(shape)) {
if (obj->is())
return JSPROP_ENUMERATE | JSPROP_PERMANENT;
- if (obj->as().getElementsHeader()->isFrozen())
- return JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY;
- return JSPROP_ENUMERATE;
+ return obj->as().getElementsHeader()->elementAttributes();
}
return shape->attributes();
diff --git a/layout/reftests/w3c-css/submitted/masking/mask-image-6.html b/layout/reftests/w3c-css/submitted/masking/mask-image-6.html
index c865e992ca7b..ac31e4cf4ef3 100644
--- a/layout/reftests/w3c-css/submitted/masking/mask-image-6.html
+++ b/layout/reftests/w3c-css/submitted/masking/mask-image-6.html
@@ -17,6 +17,7 @@
font-size: 100px;
line-height: 100px;
mask-image: url(support/transparent-100x50-blue-100x50.png);
+ mask-position: center;
mask-repeat: repeat;
}
diff --git a/layout/reftests/w3c-css/submitted/masking/mask-repeat-1-ref.html b/layout/reftests/w3c-css/submitted/masking/mask-repeat-1-ref.html
index fe36924f932b..01ed86d1270b 100644
--- a/layout/reftests/w3c-css/submitted/masking/mask-repeat-1-ref.html
+++ b/layout/reftests/w3c-css/submitted/masking/mask-repeat-1-ref.html
@@ -34,9 +34,7 @@
-
+
diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp
index 7c5099f554dd..362d5c911549 100644
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -12246,20 +12246,12 @@ CSSParserImpl::ParseImageLayersItem(
if (eCSSProperty_mask == aTable[nsStyleImageLayers::shorthand]) {
aState.mOrigin->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ORIGIN_BORDER,
eCSSUnit_Enumerated);
- aState.mRepeat->mXValue.SetIntValue(NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT,
- eCSSUnit_Enumerated);
-
- positionXArr->Item(1).SetPercentValue(0.5f);
- positionYArr->Item(1).SetPercentValue(0.5f);
} else {
aState.mOrigin->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ORIGIN_PADDING,
eCSSUnit_Enumerated);
- aState.mRepeat->mXValue.SetIntValue(NS_STYLE_IMAGELAYER_REPEAT_REPEAT,
- eCSSUnit_Enumerated);
-
- positionXArr->Item(1).SetPercentValue(0.0f);
- positionYArr->Item(1).SetPercentValue(0.0f);
}
+ positionXArr->Item(1).SetPercentValue(0.0f);
+ positionYArr->Item(1).SetPercentValue(0.0f);
aState.mSize->mXValue.SetAutoValue();
aState.mSize->mYValue.SetAutoValue();
diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp
index 7a35741a3ed3..766eea29085f 100644
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -6142,7 +6142,7 @@ nsComputedDOMStyle::DoGetMask()
firstLayer.mMaskMode != NS_STYLE_MASK_MODE_MATCH_SOURCE ||
!nsStyleImageLayers::IsInitialPositionForLayerType(
firstLayer.mPosition, nsStyleImageLayers::LayerType::Mask) ||
- !firstLayer.mRepeat.IsInitialValue(nsStyleImageLayers::LayerType::Mask) ||
+ !firstLayer.mRepeat.IsInitialValue() ||
!firstLayer.mSize.IsInitialValue() ||
!(firstLayer.mImage.GetType() == eStyleImageType_Null ||
firstLayer.mImage.GetType() == eStyleImageType_Image)) {
diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp
index 60bb76fc4fa5..f0a63f82e92d 100644
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -7259,7 +7259,7 @@ nsRuleNode::ComputeBackgroundData(void* aStartStruct,
// background-repeat: enum, inherit, initial [pair list]
nsStyleImageLayers::Repeat initialRepeat;
- initialRepeat.SetInitialValues(nsStyleImageLayers::LayerType::Background);
+ initialRepeat.SetInitialValues();
SetImageLayerPairList(aContext, *aRuleData->ValueForBackgroundRepeat(),
bg->mImage.mLayers,
parentBG->mImage.mLayers,
@@ -7308,9 +7308,7 @@ nsRuleNode::ComputeBackgroundData(void* aStartStruct,
// background-position-x/y: enum, length, percent (flags), inherit [list]
Position::Coord initialPositionCoord;
- initialPositionCoord.mPercent =
- nsStyleImageLayers::GetInitialPositionForLayerType(
- nsStyleImageLayers::LayerType::Background);
+ initialPositionCoord.mPercent = 0.0f;
initialPositionCoord.mLength = 0;
initialPositionCoord.mHasPercent = true;
@@ -9960,7 +9958,7 @@ nsRuleNode::ComputeSVGResetData(void* aStartStruct,
// mask-repeat: enum, inherit, initial [pair list]
nsStyleImageLayers::Repeat initialRepeat;
- initialRepeat.SetInitialValues(nsStyleImageLayers::LayerType::Mask);
+ initialRepeat.SetInitialValues();
SetImageLayerPairList(aContext, *aRuleData->ValueForMaskRepeat(),
svgReset->mMask.mLayers,
parentSVGReset->mMask.mLayers,
@@ -9991,9 +9989,7 @@ nsRuleNode::ComputeSVGResetData(void* aStartStruct,
// mask-position-x/y: enum, length, percent (flags), inherit [list]
Position::Coord initialPositionCoord;
- initialPositionCoord.mPercent =
- nsStyleImageLayers::GetInitialPositionForLayerType(
- nsStyleImageLayers::LayerType::Mask);
+ initialPositionCoord.mPercent = 0.0f;
initialPositionCoord.mLength = 0;
initialPositionCoord.mHasPercent = true;
diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp
index 16d9e80d9a5e..d8a20e433303 100644
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -2625,11 +2625,10 @@ nsStyleImageLayers::HasLayerWithImage() const
bool
nsStyleImageLayers::IsInitialPositionForLayerType(Position aPosition, LayerType aType)
{
- float intialValue = nsStyleImageLayers::GetInitialPositionForLayerType(aType);
- if (aPosition.mXPosition.mPercent == intialValue &&
+ if (aPosition.mXPosition.mPercent == 0.0f &&
aPosition.mXPosition.mLength == 0 &&
aPosition.mXPosition.mHasPercent &&
- aPosition.mYPosition.mPercent == intialValue &&
+ aPosition.mYPosition.mPercent == 0.0f &&
aPosition.mYPosition.mLength == 0 &&
aPosition.mYPosition.mHasPercent) {
return true;
@@ -2765,33 +2764,6 @@ nsStyleImageLayers::Size::operator==(const Size& aOther) const
(mHeightType != eLengthPercentage || mHeight == aOther.mHeight);
}
-bool
-nsStyleImageLayers::Repeat::IsInitialValue(LayerType aType) const
-{
- if (aType == LayerType::Background) {
- return mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT &&
- mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
- } else {
- MOZ_ASSERT(aType == LayerType::Mask);
- return mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT &&
- mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
- }
-}
-
-void
-nsStyleImageLayers::Repeat::SetInitialValues(LayerType aType)
-{
- if (aType == LayerType::Background) {
- mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
- mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
- } else {
- MOZ_ASSERT(aType == LayerType::Mask);
-
- mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
- mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
- }
-}
-
nsStyleImageLayers::Layer::Layer()
: mClip(NS_STYLE_IMAGELAYER_CLIP_BORDER)
, mAttachment(NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL)
@@ -2810,11 +2782,9 @@ nsStyleImageLayers::Layer::~Layer()
void
nsStyleImageLayers::Layer::Initialize(nsStyleImageLayers::LayerType aType)
{
- mRepeat.SetInitialValues(aType);
+ mRepeat.SetInitialValues();
- float initialPositionValue =
- nsStyleImageLayers::GetInitialPositionForLayerType(aType);
- mPosition.SetInitialPercentValues(initialPositionValue);
+ mPosition.SetInitialPercentValues(0.0f);
if (aType == LayerType::Background) {
mOrigin = NS_STYLE_IMAGELAYER_ORIGIN_PADDING;
diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h
index 5a7e85197a30..1d4cb3e2f027 100644
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -670,10 +670,6 @@ struct nsStyleImageLayers {
static bool IsInitialPositionForLayerType(mozilla::Position aPosition, LayerType aType);
- static float GetInitialPositionForLayerType(LayerType aType) {
- return (aType == LayerType::Background) ? 0.0f : 0.5f;
- }
-
struct Size;
friend struct Size;
struct Size {
@@ -745,7 +741,10 @@ struct nsStyleImageLayers {
// Initialize nothing
Repeat() {}
- bool IsInitialValue(LayerType aType) const;
+ bool IsInitialValue() const {
+ return mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT &&
+ mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
+ }
bool DependsOnPositioningAreaSize() const {
return mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_SPACE ||
@@ -753,7 +752,10 @@ struct nsStyleImageLayers {
}
// Initialize to initial values
- void SetInitialValues(LayerType aType);
+ void SetInitialValues() {
+ mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
+ mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
+ }
bool operator==(const Repeat& aOther) const {
return mXRepeat == aOther.mXRepeat &&
diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js
index eba700132337..337eff9cf50f 100644
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -7021,49 +7021,49 @@ if (SupportsMaskShorthand()) {
type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
/* FIXME: All mask-border-* should be added when we implement them. */
subproperties: ["mask-clip", "mask-image", "mask-mode", "mask-origin", "mask-position-x", "mask-position-y", "mask-repeat", "mask-size" , "mask-composite"],
- initial_values: [ "match-source", "none", "no-repeat", "add", "50% 50%", "center center", "50% 50% / auto", "center / auto", "center center / auto", "50% 50% / auto auto",
- "center none", "center center none", "none center", "none center center", "none 50% 50%", "center / auto none",
- "center center / auto auto none",
- "match-source none no-repeat add center center", "center center no-repeat none add", "none no-repeat add center center / auto", "center center / auto no-repeat none add match-source", "none no-repeat add 50% 50% / auto auto match-source",
+ initial_values: [ "match-source", "none", "repeat", "add", "0% 0%", "top left", "0% 0% / auto", "top left / auto", "left top / auto", "0% 0% / auto auto",
+ "top left none", "left top none", "none left top", "none top left", "none 0% 0%", "top left / auto none", "left top / auto none",
+ "top left / auto auto none",
+ "match-source none repeat add top left", "top left repeat none add", "none repeat add top left / auto", "top left / auto repeat none add match-source", "none repeat add 0% 0% / auto auto match-source",
"border-box", "border-box border-box" ],
other_values: [
- "none alpha no-repeat add center",
+ "none alpha repeat add left top",
"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
- "no-repeat url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==') alpha center add",
+ "no-repeat url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==') alpha left top add",
"repeat-x",
"repeat-y",
- "repeat",
- "none repeat-y alpha add 50% 50%",
+ "no-repeat",
+ "none repeat-y alpha add 0% 0%",
"subtract",
- "50% center subtract alpha no-repeat none",
+ "0% top subtract alpha repeat none",
"top",
"left",
- "0% 0%",
- "top left",
- "center / 100px",
- "center / contain",
- "center / cover",
+ "50% 50%",
+ "center",
+ "top / 100px",
+ "left / contain",
+ "left / cover",
"10px / 10%",
"10em / calc(20px)",
- "center center / 100px 100px",
- "center center / 100px auto",
- "center center / 100px 10%",
- "center center / 100px calc(20px)",
- "bottom right add none alpha no-repeat",
- "0% alpha",
- "alpha 0%",
- "0%",
+ "top left / 100px 100px",
+ "top left / 100px auto",
+ "top left / 100px 10%",
+ "top left / 100px calc(20px)",
+ "bottom right add none alpha repeat",
+ "50% alpha",
+ "alpha 50%",
+ "50%",
"url(#mymask)",
- "-moz-radial-gradient(10% bottom, #ffffff, black) add repeat",
- "-moz-linear-gradient(10px 10px -45deg, red, blue) no-repeat",
- "-moz-linear-gradient(10px 10px -0.125turn, red, blue) no-repeat",
- "-moz-repeating-radial-gradient(10% bottom, #ffffff, black) add repeat",
- "-moz-repeating-linear-gradient(10px 10px -45deg, red, blue) no-repeat",
+ "-moz-radial-gradient(10% bottom, #ffffff, black) add no-repeat",
+ "-moz-linear-gradient(10px 10px -45deg, red, blue) repeat",
+ "-moz-linear-gradient(10px 10px -0.125turn, red, blue) repeat",
+ "-moz-repeating-radial-gradient(10% bottom, #ffffff, black) add no-repeat",
+ "-moz-repeating-linear-gradient(10px 10px -45deg, red, blue) repeat",
"-moz-element(#test) alpha",
/* multiple mask-image */
"url(404.png), url(404.png)",
"repeat-x, subtract, none",
- "50% top url(404.png), url(404.png) 50% top",
+ "0% top url(404.png), url(404.png) 50% top",
"subtract repeat-y top left url(404.png), repeat-x alpha",
"url(404.png), -moz-linear-gradient(20px 20px -45deg, blue, green), -moz-element(#a) alpha",
"top left / contain, bottom right / cover",
@@ -7079,10 +7079,10 @@ if (SupportsMaskShorthand()) {
/* mixes with keywords have to be in correct order */
"50% left", "top 50%",
/* no quirks mode colors */
- "-moz-radial-gradient(10% bottom, ffffff, black) add repeat",
+ "-moz-radial-gradient(10% bottom, ffffff, black) add no-repeat",
/* no quirks mode lengths */
- "-moz-linear-gradient(10 10px -45deg, red, blue) no-repeat",
- "-moz-linear-gradient(10px 10 -45deg, red, blue) no-repeat",
+ "-moz-linear-gradient(10 10px -45deg, red, blue) repeat",
+ "-moz-linear-gradient(10px 10 -45deg, red, blue) repeat",
"linear-gradient(red -99, yellow, green, blue 120%)",
/* bug 258080: don't accept background-position separated */
"left url(404.png) top", "top url(404.png) left",
@@ -7147,9 +7147,8 @@ if (SupportsMaskShorthand()) {
domProp: "maskPosition",
inherited: false,
type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
- initial_values: [ "left 50% top 50%", "left 50% center", "center", "center center", "50% 50%", "50% center", "center 50%" ],
- other_values: [ "top", "left", "right", "bottom", "center bottom", "bottom center", "center right", "right center", "center top", "top center", "center left", "left center", "right bottom", "bottom right", "0%", "top left, top left", "top left, top right", "top right, top left", "left top, 0% 0%", "10% 20%, 30%, 40%", "top left, bottom right", "right bottom, left top", "0%", "0px", "30px", "0%, 10%, 20%, 30%", "top, top, top, top, top",
- "top 0% left 0%", "top 0% left", "top left", "left top", "0% 0%", "0% top", "left 0%",
+ initial_values: [ "top 0% left 0%", "top 0% left", "top left", "left top", "0% 0%", "0% top", "left 0%" ],
+ other_values: [ "top", "left", "right", "bottom", "center", "center bottom", "bottom center", "center right", "right center", "center top", "top center", "center left", "left center", "right bottom", "bottom right", "50%", "top left, top left", "top left, top right", "top right, top left", "left top, 0% 0%", "10% 20%, 30%, 40%", "top left, bottom right", "right bottom, left top", "0%", "0px", "30px", "0%, 10%, 20%, 30%", "top, top, top, top, top",
"calc(20px)",
"calc(20px) 10px",
"10px calc(20px)",
@@ -7191,8 +7190,8 @@ if (SupportsMaskShorthand()) {
domProp: "maskPositionX",
inherited: false,
type: CSS_TYPE_LONGHAND,
- initial_values: [ "center", "50%" ],
- other_values: [ "right", "left", "0%", "left, left", "left, right", "right, left", "left, 0%", "10%, 20%, 40%", "0px", "30px", "0%, 10%, 20%, 30%", "left, left, left, left, left",
+ initial_values: [ "left", "0%" ],
+ other_values: [ "right", "center", "50%", "center, center", "center, right", "right, center", "center, 50%", "10%, 20%, 40%", "1px", "30px", "50%, 10%, 20%, 30%", "center, center, center, center, center",
"calc(20px)",
"calc(20px + 1em)",
"calc(20px / 2)",
@@ -7217,8 +7216,8 @@ if (SupportsMaskShorthand()) {
domProp: "maskPositionY",
inherited: false,
type: CSS_TYPE_LONGHAND,
- initial_values: [ "center", "50%" ],
- other_values: [ "bottom", "top", "0%", "top, top", "top, bottom", "bottom, top", "top, 0%", "10%, 20%, 40%", "0px", "30px", "0%, 10%, 20%, 30%", "top, top, top, top, top",
+ initial_values: [ "top", "0%" ],
+ other_values: [ "bottom", "center", "50%", "center, center", "center, bottom", "bottom, center", "center, 0%", "10%, 20%, 40%", "1px", "30px", "50%, 10%, 20%, 30%", "center, center, center, center, center",
"calc(20px)",
"calc(20px + 1em)",
"calc(20px / 2)",
@@ -7243,15 +7242,16 @@ if (SupportsMaskShorthand()) {
domProp: "maskRepeat",
inherited: false,
type: CSS_TYPE_LONGHAND,
- initial_values: [ "no-repeat", "no-repeat no-repeat" ],
- other_values: [ "repeat-x", "repeat-y", "repeat",
+ initial_values: [ "repeat", "repeat repeat" ],
+ other_values: [ "repeat-x", "repeat-y", "no-repeat",
"repeat-x, repeat-x",
- "no-repeat, repeat",
- "repeat-y, repeat, repeat-y",
- "no-repeat, no-repeat, no-repeat",
- "no-repeat repeat",
+ "repeat, no-repeat",
+ "repeat-y, no-repeat, repeat-y",
+ "repeat, repeat, repeat",
+ "repeat no-repeat",
+ "no-repeat repeat",
+ "no-repeat no-repeat",
"repeat no-repeat",
- "repeat repeat",
"no-repeat no-repeat, no-repeat no-repeat",
],
invalid_values: [ "repeat repeat repeat",
diff --git a/layout/style/test/test_computed_style.html b/layout/style/test/test_computed_style.html
index 194bb85268d3..1961f99e9908 100644
--- a/layout/style/test/test_computed_style.html
+++ b/layout/style/test/test_computed_style.html
@@ -277,14 +277,14 @@ var noframe_container = document.getElementById("content");
"mask-mode": [
"alpha", "luminance"
],
- // any mask-position value other then "50%" "50% 50%" "center"
+ // any mask-position value other then "0%" "top" "left"
// "center center".
"mask-position": [
- "left", "right", "top", "bottom", "0%", "100%"
+ "0%", "center", "right", "bottom", "50%", "100%"
],
- // any mask-repeat value other then "no-repeat" "no-repeat no-repeat".
+ // any mask-repeat value other then "repeat" "repeat repeat".
"mask-repeat": [
- "repeat-x", "repeat-y", "repeat", "space", "round"
+ "repeat-x", "repeat-y", "no-repeat", "space", "round"
],
// any mask-size value other then "auto" "auto auto".
"mask-size": [
@@ -310,10 +310,10 @@ var noframe_container = document.getElementById("content");
"match-source"
],
"mask-position": [
- "50%", "50% 50%", "center"
+ "0% 0%", "left top"
],
"mask-repeat": [
- "no-repeat", "no-repeat no-repeat"
+ "repeat", "repeat repeat"
],
"mask-size": [
"auto", "auto auto"
diff --git a/layout/xul/nsMenuPopupFrame.cpp b/layout/xul/nsMenuPopupFrame.cpp
index 5437d659bd58..6e706e49c228 100644
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -880,6 +880,13 @@ nsMenuPopupFrame::ShowPopup(bool aIsContextMenu)
// Clear mouse capture when a popup is opened.
if (mPopupType == ePopupTypeMenu) {
+ EventStateManager* activeESM =
+ static_cast(
+ EventStateManager::GetActiveEventStateManager());
+ if (activeESM) {
+ EventStateManager::ClearGlobalActiveContent(activeESM);
+ }
+
nsIPresShell::SetCapturingContent(nullptr, 0);
}
diff --git a/media/mtransport/nricectx.cpp b/media/mtransport/nricectx.cpp
index a12b8d2d9dad..8fbd6894a1ae 100644
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -243,11 +243,6 @@ nsresult NrIceTurnServer::ToNicerTurnStruct(nr_ice_turn_server *server) const {
if (NS_FAILED(rv))
return rv;
- if (username_.empty())
- return NS_ERROR_INVALID_ARG;
- if (password_.empty())
- return NS_ERROR_INVALID_ARG;
-
if (!(server->username=r_strdup(username_.c_str())))
return NS_ERROR_OUT_OF_MEMORY;
diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
index fb05287c08b3..08810a92dc3a 100644
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
@@ -672,7 +672,7 @@ static int nr_ice_get_default_local_address(nr_ice_ctx *ctx, int ip_version, nr_
if ((r=nr_ice_get_default_address(ctx, ip_version, &default_addr)))
ABORT(r);
- for(i=0; iaddr, &default_addr)))
+ ABORT(r);
+ strlcpy(addrp->addr.ifname, "default route", sizeof(addrp->addr.ifname));
+ }
_status=0;
abort:
@@ -700,11 +704,10 @@ static int nr_ice_get_local_addresses(nr_ice_ctx *ctx)
if (!ctx->local_addrs) {
/* First, gather all the local addresses we have */
if((r=nr_stun_find_local_addresses(local_addrs,MAXADDRS,&addr_ct))) {
- r_log(LOG_ICE,LOG_ERR,"ICE(%s): unable to find local addresses",ctx->label);
- ABORT(r);
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): unable to gather local addresses, trying default route",ctx->label);
}
- if (ctx->force_net_interface[0]) {
+ if (ctx->force_net_interface[0] && addr_ct) {
/* Limit us to only addresses on a single interface */
int force_addr_ct = 0;
for(i=0;iflags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS) {
+ if ((!addr_ct) || (ctx->flags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS)) {
/* Get just the default IPv4 and IPv6 addrs */
if(!nr_ice_get_default_local_address(ctx, NR_IPV4, local_addrs, addr_ct,
&default_addrs[default_addr_ct])) {
@@ -731,6 +734,10 @@ static int nr_ice_get_local_addresses(nr_ice_ctx *ctx)
&default_addrs[default_addr_ct])) {
++default_addr_ct;
}
+ if (!default_addr_ct) {
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): failed to find default addresses",ctx->label);
+ ABORT(R_FAILED);
+ }
addrs = default_addrs;
addr_ct = default_addr_ct;
}
diff --git a/media/mtransport/third_party/nICEr/src/net/local_addr.c b/media/mtransport/third_party/nICEr/src/net/local_addr.c
index fd710b0ef1f5..c251f2215d56 100644
--- a/media/mtransport/third_party/nICEr/src/net/local_addr.c
+++ b/media/mtransport/third_party/nICEr/src/net/local_addr.c
@@ -56,5 +56,6 @@ int nr_local_addr_fmt_info_string(nr_local_addr *addr, char *buf, int len)
snprintf(buf, len, "%s%s, estimated speed: %d kbps",
vpn, type, addr->interface.estimated_speed);
+ buf[len - 1] = '\0';
return (0);
}
diff --git a/media/mtransport/third_party/nICEr/src/stun/addrs.c b/media/mtransport/third_party/nICEr/src/stun/addrs.c
index ef13621f095a..6c7bc7a4bb91 100644
--- a/media/mtransport/third_party/nICEr/src/stun/addrs.c
+++ b/media/mtransport/third_party/nICEr/src/stun/addrs.c
@@ -149,9 +149,11 @@ abort:
static int
stun_get_win32_addrs(nr_local_addr addrs[], int maxaddrs, int *count)
{
- int r,_status;
+ int r, _status;
PIP_ADAPTER_ADDRESSES AdapterAddresses = NULL, tmpAddress = NULL;
- ULONG buflen;
+ // recomended per https://msdn.microsoft.com/en-us/library/windows/desktop/aa365915(v=vs.85).aspx
+ static const ULONG initialBufLen = 15000;
+ ULONG buflen = initialBufLen;
char bin_hashed_ifname[NR_MD5_HASH_LENGTH];
char hex_hashed_ifname[MAXIFNAME];
int n = 0;
@@ -159,31 +161,30 @@ stun_get_win32_addrs(nr_local_addr addrs[], int maxaddrs, int *count)
*count = 0;
if (maxaddrs <= 0)
- ABORT(R_INTERNAL);
+ ABORT(R_BAD_ARGS);
- /* Call GetAdaptersAddresses() twice. First, just to get the buf length */
+ /* According to MSDN (see above) we have try GetAdapterAddresses() multiple times */
+ for (n = 0; n < 5; n++) {
+ AdapterAddresses = (PIP_ADAPTER_ADDRESSES) RMALLOC(buflen);
+ if (AdapterAddresses == NULL) {
+ r_log(NR_LOG_STUN, LOG_ERR, "Error allocating buf for GetAdaptersAddresses()");
+ ABORT(R_NO_MEMORY);
+ }
- buflen = 0;
+ r = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, NULL, AdapterAddresses, &buflen);
+ if (r == NO_ERROR) {
+ break;
+ }
+ r_log(NR_LOG_STUN, LOG_ERR, "GetAdaptersAddresses() returned error (%d)", r);
+ RFREE(AdapterAddresses);
+ }
- r = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, AdapterAddresses, &buflen);
- if (r != ERROR_BUFFER_OVERFLOW) {
- r_log(NR_LOG_STUN, LOG_ERR, "Error getting buf len from GetAdaptersAddresses()");
+ if (n >= 5) {
+ r_log(NR_LOG_STUN, LOG_ERR, "5 failures calling GetAdaptersAddresses()");
ABORT(R_INTERNAL);
}
- AdapterAddresses = (PIP_ADAPTER_ADDRESSES) RMALLOC(buflen);
- if (AdapterAddresses == NULL) {
- r_log(NR_LOG_STUN, LOG_ERR, "Error allocating buf for GetAdaptersAddresses()");
- ABORT(R_NO_MEMORY);
- }
-
- /* for real, this time */
-
- r = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, AdapterAddresses, &buflen);
- if (r != NO_ERROR) {
- r_log(NR_LOG_STUN, LOG_ERR, "Error getting addresses from GetAdaptersAddresses()");
- ABORT(R_INTERNAL);
- }
+ n = 0;
/* Loop through the adapters */
@@ -258,7 +259,10 @@ stun_getifaddrs(nr_local_addr addrs[], int maxaddrs, int *count)
struct ifaddrs* if_addrs_head=NULL;
struct ifaddrs* if_addr;
- *count=0;
+ *count = 0;
+
+ if (maxaddrs <= 0)
+ ABORT(R_BAD_ARGS);
if (getifaddrs(&if_addrs_head) == -1) {
r_log(NR_LOG_STUN, LOG_ERR, "getifaddrs error e = %d", errno);
@@ -395,6 +399,7 @@ nr_stun_remove_duplicate_addrs(nr_local_addr addrs[], int remove_loopback, int r
*count = n;
+ memset(addrs, 0, *count * sizeof(*addrs));
/* copy temporary array into passed in/out array */
for (i = 0; i < *count; ++i) {
if ((r=nr_local_addr_copy(&addrs[i], &tmp[i])))
@@ -412,7 +417,7 @@ nr_stun_remove_duplicate_addrs(nr_local_addr addrs[], int remove_loopback, int r
int
nr_stun_get_addrs(nr_local_addr addrs[], int maxaddrs, int drop_loopback, int drop_link_local, int *count)
{
- int _status=0;
+ int r,_status=0;
int i;
char typestr[100];
@@ -422,14 +427,16 @@ nr_stun_get_addrs(nr_local_addr addrs[], int maxaddrs, int drop_loopback, int dr
_status = stun_getifaddrs(addrs, maxaddrs, count);
#endif
- nr_stun_remove_duplicate_addrs(addrs, drop_loopback, drop_link_local, count);
+ if ((r=nr_stun_remove_duplicate_addrs(addrs, drop_loopback, drop_link_local, count)))
+ ABORT(r);
for (i = 0; i < *count; ++i) {
- nr_local_addr_fmt_info_string(addrs+i,typestr,sizeof(typestr));
- r_log(NR_LOG_STUN, LOG_DEBUG, "Address %d: %s on %s, type: %s\n",
+ nr_local_addr_fmt_info_string(addrs+i,typestr,sizeof(typestr));
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Address %d: %s on %s, type: %s\n",
i,addrs[i].addr.as_string,addrs[i].addr.ifname,typestr);
}
+abort:
return _status;
}
diff --git a/media/mtransport/third_party/nICEr/src/stun/stun_util.c b/media/mtransport/third_party/nICEr/src/stun/stun_util.c
index a881e5f97ed8..e347e542f482 100644
--- a/media/mtransport/third_party/nICEr/src/stun/stun_util.c
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_util.c
@@ -119,12 +119,12 @@ int
nr_stun_find_local_addresses(nr_local_addr addrs[], int maxaddrs, int *count)
{
int r,_status;
- NR_registry *children = 0;
+ //NR_registry *children = 0;
+
+ *count = 0;
if ((r=NR_reg_get_child_count(NR_STUN_REG_PREF_ADDRESS_PRFX, (unsigned int*)count)))
- if (r == R_NOT_FOUND)
- *count = 0;
- else
+ if (r != R_NOT_FOUND)
ABORT(r);
if (*count == 0) {
@@ -182,7 +182,7 @@ nr_stun_find_local_addresses(nr_local_addr addrs[], int maxaddrs, int *count)
_status=0;
abort:
- RFREE(children);
+ //RFREE(children);
return _status;
}
diff --git a/media/webrtc/trunk/webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_info_objc.mm b/media/webrtc/trunk/webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_info_objc.mm
index 1ee1029b51fb..e309df5996c6 100644
--- a/media/webrtc/trunk/webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_info_objc.mm
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_info_objc.mm
@@ -162,7 +162,14 @@ using namespace videocapturemodule;
*width = videoDimensions.width;
*height = videoDimensions.height;
- *maxFPS = maxFrameRateRange.maxFrameRate;
+
+ // This is to fix setCaptureHeight() which fails for some webcams supporting non-integer framerates.
+ // In setCaptureHeight(), we match the best framerate range by searching a range whose max framerate
+ // is most close to (but smaller than or equal to) the target. Since maxFPS of capability is integer,
+ // we fill in the capability maxFPS with the floor value (e.g., 29) of the real supported fps
+ // (e.g., 29.97). If the target is set to 29, we failed to match the best format with max framerate
+ // 29.97 since it is over the target. Therefore, we need to return a ceiling value as the maxFPS here.
+ *maxFPS = static_cast(ceil(maxFrameRateRange.maxFrameRate));
*rawType = [VideoCaptureMacAVFoundationUtility fourCCToRawVideoType:CMFormatDescriptionGetMediaSubType(format.formatDescription)];
return [NSNumber numberWithInt:0];
diff --git a/media/webrtc/trunk/webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_objc.mm b/media/webrtc/trunk/webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_objc.mm
index 467817303cb9..dc6b696f5cb7 100644
--- a/media/webrtc/trunk/webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_objc.mm
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/mac/avfoundation/video_capture_avfoundation_objc.mm
@@ -238,32 +238,36 @@ using namespace videocapturemodule;
return;
}
+ CMFormatDescriptionRef formatDescription =
+ CMSampleBufferGetFormatDescription(sampleBuffer);
+ webrtc::RawVideoType rawType =
+ [VideoCaptureMacAVFoundationUtility fourCCToRawVideoType:CMFormatDescriptionGetMediaSubType(formatDescription)];
+ CMVideoDimensions dimensions =
+ CMVideoFormatDescriptionGetDimensions(formatDescription);
+
VideoCaptureCapability tempCaptureCapability;
- tempCaptureCapability.width = _frameWidth;
- tempCaptureCapability.height = _frameHeight;
+ tempCaptureCapability.width = dimensions.width;
+ tempCaptureCapability.height = dimensions.height;
tempCaptureCapability.maxFPS = _frameRate;
- tempCaptureCapability.rawType = _rawType;
+ tempCaptureCapability.rawType = rawType;
- if (webrtc::kVideoMJPEG == _rawType) {
- CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
+ CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
- if (blockBuffer) {
- char* baseAddress;
- size_t frameSize;
- size_t lengthAtOffset;
- CMBlockBufferGetDataPointer(blockBuffer, 0, &lengthAtOffset, &frameSize, &baseAddress);
+ if (blockBuffer) {
+ char* baseAddress;
+ size_t frameSize;
+ size_t lengthAtOffset;
+ CMBlockBufferGetDataPointer(blockBuffer, 0, &lengthAtOffset, &frameSize, &baseAddress);
- NSAssert(lengthAtOffset == frameSize, @"lengthAtOffset != frameSize)");
+ NSAssert(lengthAtOffset == frameSize, @"lengthAtOffset != frameSize)");
- _owner->IncomingFrame((unsigned char*)baseAddress, frameSize,
+ _owner->IncomingFrame((unsigned char*)baseAddress, frameSize,
tempCaptureCapability, 0);
- }
} else {
// Get a CMSampleBuffer's Core Video image buffer for the media data
CVImageBufferRef videoFrame = CMSampleBufferGetImageBuffer(sampleBuffer);
- const int kFlags = 0;
- if (CVPixelBufferLockBaseAddress(videoFrame, kFlags) == kCVReturnSuccess) {
+ if (CVPixelBufferLockBaseAddress(videoFrame, kCVPixelBufferLock_ReadOnly) == kCVReturnSuccess) {
void* baseAddress = CVPixelBufferGetBaseAddress(videoFrame);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(videoFrame);
size_t frameHeight = CVPixelBufferGetHeight(videoFrame);
@@ -271,7 +275,7 @@ using namespace videocapturemodule;
_owner->IncomingFrame((unsigned char*)baseAddress, frameSize,
tempCaptureCapability, 0);
- CVPixelBufferUnlockBaseAddress(videoFrame, kFlags);
+ CVPixelBufferUnlockBaseAddress(videoFrame, kCVPixelBufferLock_ReadOnly);
}
}
[_lock unlock];
diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js
index 8cad5465ec61..ba4b99f08129 100644
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -624,6 +624,8 @@ pref("media.android-remote-codec.enabled", false);
// Enable MSE
pref("media.mediasource.enabled", true);
+pref("media.mediadrm-widevinecdm.visible", true);
+
// optimize images memory usage
pref("image.downscale-during-decode.enabled", true);
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/MediaDrmProxy.java b/mobile/android/base/java/org/mozilla/gecko/media/MediaDrmProxy.java
new file mode 100644
index 000000000000..b9253f057fd9
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/media/MediaDrmProxy.java
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+package org.mozilla.gecko.media;
+
+import java.util.UUID;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.AppConstants;
+
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaCrypto;
+import android.media.MediaDrm;
+import android.util.Log;
+import android.os.Build;
+
+public final class MediaDrmProxy {
+ private static final String LOGTAG = "GeckoMediaDrmProxy";
+ private static final boolean DEBUG = false;
+ private static final UUID WIDEVINE_SCHEME_UUID =
+ new UUID(0xedef8ba979d64aceL, 0xa3c827dcd51d21edL);
+
+ private static final String WIDEVINE_KEY_SYSTEM = "com.widevine.alpha";
+ @WrapForJNI
+ private static final String AAC = "audio/mp4a-latm";
+ @WrapForJNI
+ private static final String AVC = "video/avc";
+ @WrapForJNI
+ private static final String VORBIS = "audio/vorbis";
+ @WrapForJNI
+ private static final String VP8 = "video/x-vnd.on2.vp8";
+ @WrapForJNI
+ private static final String VP9 = "video/x-vnd.on2.vp9";
+ @WrapForJNI
+ private static final String OPUS = "audio/opus";
+
+ private static boolean isSystemSupported() {
+ // Support versions >= LOLLIPOP
+ if (AppConstants.Versions.preLollipop) {
+ if (DEBUG) Log.d(LOGTAG, "System Not supported !!, current SDK version is " + Build.VERSION.SDK_INT);
+ return false;
+ }
+ return true;
+ }
+
+ @WrapForJNI
+ public static boolean isSchemeSupported(String keySystem) {
+ if (!isSystemSupported()) {
+ return false;
+ }
+ if (keySystem.equals(WIDEVINE_KEY_SYSTEM)) {
+ return MediaDrm.isCryptoSchemeSupported(WIDEVINE_SCHEME_UUID)
+ && MediaCrypto.isCryptoSchemeSupported(WIDEVINE_SCHEME_UUID);
+ }
+ if (DEBUG) Log.d(LOGTAG, "isSchemeSupported key sytem = " + keySystem);
+ return false;
+ }
+
+ @WrapForJNI
+ public static boolean IsCryptoSchemeSupported(String keySystem,
+ String container) {
+ if (!isSystemSupported()) {
+ return false;
+ }
+ if (keySystem.equals(WIDEVINE_KEY_SYSTEM)) {
+ return MediaDrm.isCryptoSchemeSupported(WIDEVINE_SCHEME_UUID, container);
+ }
+ if (DEBUG) Log.d(LOGTAG, "cannot decrypt key sytem = " + keySystem + ", container = " + container);
+ return false;
+ }
+
+ @WrapForJNI
+ public static boolean CanDecode(String mimeType) {
+ for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
+ MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
+ if (info.isEncoder()) {
+ continue;
+ }
+ for (String m : info.getSupportedTypes()) {
+ if (m.equals(mimeType)) {
+ return true;
+ }
+ }
+ }
+ if (DEBUG) Log.d(LOGTAG, "cannot decode mimetype = " + mimeType);
+ return false;
+ }
+}
diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build
index c11da8b9bba4..6553ac37f000 100644
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -557,6 +557,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'media/FormatParam.java',
'media/JellyBeanAsyncCodec.java',
'media/MediaControlService.java',
+ 'media/MediaDrmProxy.java',
'media/MediaManager.java',
'media/RemoteManager.java',
'media/Sample.java',
diff --git a/python/mozboot/mozboot/archlinux.py b/python/mozboot/mozboot/archlinux.py
index 01142715f886..12ce6b4a4692 100644
--- a/python/mozboot/mozboot/archlinux.py
+++ b/python/mozboot/mozboot/archlinux.py
@@ -168,8 +168,8 @@ class ArchlinuxBootstrapper(BaseBootstrapper):
self.run_as_root(command)
- def run(self, command):
- subprocess.check_call(command, stdin=sys.stdin)
+ def run(self, command, env=None):
+ subprocess.check_call(command, stdin=sys.stdin, env=env)
def download(self, uri):
command = ['curl', '-L', '-O', uri]
@@ -189,8 +189,10 @@ class ArchlinuxBootstrapper(BaseBootstrapper):
def makepkg(self, name):
command = ['makepkg', '-s']
- self.run(command)
- pack = glob.glob(name + '*.tar.xz')[0]
+ makepkg_env = os.environ.copy()
+ makepkg_env['PKGEXT'] = '.pkg.tar.xz'
+ self.run(command, env=makepkg_env)
+ pack = glob.glob(name + '*.pkg.tar.xz')[0]
command = ['pacman', '-U']
if self.no_interactive:
command.append('--noconfirm')
diff --git a/browser/locales/searchjson.py b/python/mozbuild/mozbuild/action/generate_searchjson.py
similarity index 100%
rename from browser/locales/searchjson.py
rename to python/mozbuild/mozbuild/action/generate_searchjson.py
diff --git a/browser/locales/searchplugins.py b/python/mozbuild/mozbuild/action/output_searchplugins_list.py
similarity index 100%
rename from browser/locales/searchplugins.py
rename to python/mozbuild/mozbuild/action/output_searchplugins_list.py
diff --git a/python/mozbuild/mozbuild/backend/common.py b/python/mozbuild/mozbuild/backend/common.py
index cc8e74be800a..12b2a27c458b 100644
--- a/python/mozbuild/mozbuild/backend/common.py
+++ b/python/mozbuild/mozbuild/backend/common.py
@@ -4,6 +4,7 @@
from __future__ import absolute_import, unicode_literals
+import cPickle as pickle
import itertools
import json
import os
@@ -370,19 +371,18 @@ class CommonBackend(BuildBackend):
# Write out a machine-readable file describing every test.
topobjdir = self.environment.topobjdir
- with self._write_file(mozpath.join(topobjdir, 'all-tests.json')) as fh:
- json.dump(self._test_manager.tests_by_path, fh)
+ with self._write_file(mozpath.join(topobjdir, 'all-tests.pkl'), mode='rb') as fh:
+ pickle.dump(dict(self._test_manager.tests_by_path), fh, protocol=2)
- with self._write_file(mozpath.join(topobjdir, 'test-defaults.json')) as fh:
- json.dump(self._test_manager.manifest_defaults, fh)
+ with self._write_file(mozpath.join(topobjdir, 'test-defaults.pkl'), mode='rb') as fh:
+ pickle.dump(self._test_manager.manifest_defaults, fh, protocol=2)
- path = mozpath.join(self.environment.topobjdir, 'test-installs.json')
- with self._write_file(path) as fh:
- json.dump({k: v for k, v in self._test_manager.installs_by_path.items()
- if k in self._test_manager.deferred_installs},
- fh,
- sort_keys=True,
- indent=4)
+ path = mozpath.join(self.environment.topobjdir, 'test-installs.pkl')
+ with self._write_file(path, mode='rb') as fh:
+ pickle.dump({k: v for k, v in self._test_manager.installs_by_path.items()
+ if k in self._test_manager.deferred_installs},
+ fh,
+ protocol=2)
# Write out a machine-readable file describing binaries.
with self._write_file(mozpath.join(topobjdir, 'binaries.json')) as fh:
diff --git a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
index df4fa958d3eb..87f50f497379 100644
--- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
+import cPickle as pickle
import json
import os
import unittest
@@ -524,11 +525,11 @@ class TestRecursiveMakeBackend(BackendTester):
'[include:xpcshell.ini]',
])
- all_tests_path = mozpath.join(env.topobjdir, 'all-tests.json')
+ all_tests_path = mozpath.join(env.topobjdir, 'all-tests.pkl')
self.assertTrue(os.path.exists(all_tests_path))
- with open(all_tests_path, 'rt') as fh:
- o = json.load(fh)
+ with open(all_tests_path, 'rb') as fh:
+ o = pickle.load(fh)
self.assertIn('xpcshell.js', o)
self.assertIn('dir1/test_bar.js', o)
@@ -550,12 +551,12 @@ class TestRecursiveMakeBackend(BackendTester):
def test_test_manifest_deffered_installs_written(self):
"""Shared support files are written to their own data file by the backend."""
env = self._consume('test-manifest-shared-support', RecursiveMakeBackend)
- all_tests_path = mozpath.join(env.topobjdir, 'all-tests.json')
+ all_tests_path = mozpath.join(env.topobjdir, 'all-tests.pkl')
self.assertTrue(os.path.exists(all_tests_path))
- test_installs_path = mozpath.join(env.topobjdir, 'test-installs.json')
+ test_installs_path = mozpath.join(env.topobjdir, 'test-installs.pkl')
with open(test_installs_path, 'r') as fh:
- test_installs = json.load(fh)
+ test_installs = pickle.load(fh)
self.assertEqual(set(test_installs.keys()),
set(['child/test_sub.js',
@@ -572,7 +573,7 @@ class TestRecursiveMakeBackend(BackendTester):
# First, read the generated for ini manifest contents.
m = InstallManifest(path=test_files_manifest)
- # Then, synthesize one from the test-installs.json file. This should
+ # Then, synthesize one from the test-installs.pkl file. This should
# allow us to re-create a subset of the above.
synthesized_manifest = InstallManifest()
for item, installs in test_installs.items():
@@ -850,11 +851,11 @@ class TestRecursiveMakeBackend(BackendTester):
"""Ensure test suites honor package_tests=False."""
env = self._consume('test-manifests-package-tests', RecursiveMakeBackend)
- all_tests_path = mozpath.join(env.topobjdir, 'all-tests.json')
+ all_tests_path = mozpath.join(env.topobjdir, 'all-tests.pkl')
self.assertTrue(os.path.exists(all_tests_path))
- with open(all_tests_path, 'rt') as fh:
- o = json.load(fh)
+ with open(all_tests_path, 'rb') as fh:
+ o = pickle.load(fh)
self.assertIn('mochitest.js', o)
self.assertIn('not_packaged.java', o)
diff --git a/python/mozbuild/mozbuild/test/test_preprocessor.py b/python/mozbuild/mozbuild/test/test_preprocessor.py
index b0d0748afedd..9aba948538a9 100644
--- a/python/mozbuild/mozbuild/test/test_preprocessor.py
+++ b/python/mozbuild/mozbuild/test/test_preprocessor.py
@@ -619,13 +619,13 @@ class TestPreprocessor(unittest.TestCase):
with MockedOpen({'f': '#include foo\n'}):
with self.assertRaises(Preprocessor.Error) as e:
self.pp.do_include('f')
- self.assertEqual(e.key, 'FILE_NOT_FOUND')
+ self.assertEqual(e.exception.key, 'FILE_NOT_FOUND')
def test_include_undefined_variable(self):
with MockedOpen({'f': '#filter substitution\n#include @foo@\n'}):
with self.assertRaises(Preprocessor.Error) as e:
self.pp.do_include('f')
- self.assertEqual(e.key, 'UNDEFINED_VAR')
+ self.assertEqual(e.exception.key, 'UNDEFINED_VAR')
def test_include_literal_at(self):
files = {
diff --git a/python/mozbuild/mozbuild/test/test_testing.py b/python/mozbuild/mozbuild/test/test_testing.py
index 853109413482..e71892e241ae 100644
--- a/python/mozbuild/mozbuild/test/test_testing.py
+++ b/python/mozbuild/mozbuild/test/test_testing.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
+import cPickle as pickle
import os
import shutil
import tempfile
@@ -21,8 +22,7 @@ from mozbuild.testing import (
)
-ALL_TESTS_JSON = b'''
-{
+ALL_TESTS = {
"accessible/tests/mochitest/actions/test_anchors.html": [
{
"dir_relpath": "accessible/tests/mochitest/actions",
@@ -154,11 +154,11 @@ ALL_TESTS_JSON = b'''
"tags": "devtools"
}
]
-}'''.strip()
+}
-TEST_DEFAULTS = b'''{
- "/Users/gps/src/firefox/toolkit/mozapps/update/test/unit/xpcshell_updater.ini": {"support-files": "\\ndata/**\\nxpcshell_updater.ini"}
-}'''
+TEST_DEFAULTS = {
+ "/Users/gps/src/firefox/toolkit/mozapps/update/test/unit/xpcshell_updater.ini": {"support-files": "\ndata/**\nxpcshell_updater.ini"}
+}
class Base(unittest.TestCase):
@@ -172,13 +172,13 @@ class Base(unittest.TestCase):
self._temp_files = []
def _get_test_metadata(self):
- all_tests = NamedTemporaryFile()
- all_tests.write(ALL_TESTS_JSON)
+ all_tests = NamedTemporaryFile(mode='wb')
+ pickle.dump(ALL_TESTS, all_tests)
all_tests.flush()
self._temp_files.append(all_tests)
- test_defaults = NamedTemporaryFile()
- test_defaults.write(TEST_DEFAULTS)
+ test_defaults = NamedTemporaryFile(mode='wb')
+ pickle.dump(TEST_DEFAULTS, test_defaults)
test_defaults.flush()
self._temp_files.append(test_defaults)
@@ -251,10 +251,10 @@ class TestTestResolver(Base):
topobjdir = tempfile.mkdtemp()
self._temp_dirs.append(topobjdir)
- with open(os.path.join(topobjdir, 'all-tests.json'), 'wt') as fh:
- fh.write(ALL_TESTS_JSON)
- with open(os.path.join(topobjdir, 'test-defaults.json'), 'wt') as fh:
- fh.write(TEST_DEFAULTS)
+ with open(os.path.join(topobjdir, 'all-tests.pkl'), 'wb') as fh:
+ pickle.dump(ALL_TESTS, fh)
+ with open(os.path.join(topobjdir, 'test-defaults.pkl'), 'wb') as fh:
+ pickle.dump(TEST_DEFAULTS, fh)
o = MozbuildObject(self.FAKE_TOPSRCDIR, None, None, topobjdir=topobjdir)
diff --git a/python/mozbuild/mozbuild/testing.py b/python/mozbuild/mozbuild/testing.py
index ac891d4b9c98..3409f98219c5 100644
--- a/python/mozbuild/mozbuild/testing.py
+++ b/python/mozbuild/mozbuild/testing.py
@@ -4,7 +4,7 @@
from __future__ import absolute_import, unicode_literals
-import json
+import cPickle as pickle
import os
import sys
@@ -56,12 +56,12 @@ class TestMetadata(object):
self._tests_by_flavor = defaultdict(set)
self._test_dirs = set()
- with open(all_tests, 'rt') as fh:
- test_data = json.load(fh)
+ with open(all_tests, 'rb') as fh:
+ test_data = pickle.load(fh)
defaults = None
if test_defaults:
- with open(test_defaults, 'rt') as fh:
- defaults = json.load(fh)
+ with open(test_defaults, 'rb') as fh:
+ defaults = pickle.load(fh)
for path, tests in test_data.items():
for metadata in tests:
if defaults:
@@ -178,14 +178,14 @@ class TestResolver(MozbuildObject):
# If installing tests is going to result in re-generating the build
# backend, we need to do this here, so that the updated contents of
- # all-tests.json make it to the set of tests to run.
+ # all-tests.pkl make it to the set of tests to run.
self._run_make(target='run-tests-deps', pass_thru=True,
print_directory=False)
self._tests = TestMetadata(os.path.join(self.topobjdir,
- 'all-tests.json'),
+ 'all-tests.pkl'),
test_defaults=os.path.join(self.topobjdir,
- 'test-defaults.json'))
+ 'test-defaults.pkl'))
self._test_rewrites = {
'a11y': os.path.join(self.topobjdir, '_tests', 'testing',
@@ -417,9 +417,9 @@ def _resolve_installs(paths, topobjdir, manifest):
by the build backend corresponding to those keys, and add them
to the given manifest.
"""
- filename = os.path.join(topobjdir, 'test-installs.json')
- with open(filename, 'r') as fh:
- resolved_installs = json.load(fh)
+ filename = os.path.join(topobjdir, 'test-installs.pkl')
+ with open(filename, 'rb') as fh:
+ resolved_installs = pickle.load(fh)
for path in paths:
path = path[2:]
diff --git a/services/sync/modules/addonsreconciler.js b/services/sync/modules/addonsreconciler.js
index b24130c13505..a60fc8d56667 100644
--- a/services/sync/modules/addonsreconciler.js
+++ b/services/sync/modules/addonsreconciler.js
@@ -445,6 +445,7 @@ AddonsReconciler.prototype = {
}
let record = this._addons[id];
+ record.isSyncable = addon.isSyncable;
if (!record.installed) {
// It is possible the record is marked as uninstalled because an
diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json
index 1712dcd2dcba..13ddf9186f08 100644
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -3822,6 +3822,13 @@
"bug_numbers": [1283007],
"description": "Time spent fallocating Variable-Length PrefixSet (ms)"
},
+ "URLCLASSIFIER_VLPS_LOAD_CORRUPT": {
+ "alert_emails": ["safebrowsing-telemetry@mozilla.org"],
+ "expires_in_version": "58",
+ "kind": "boolean",
+ "bug_numbers": [1305581],
+ "description": "Whether or not a variable-length prefix set loaded from disk is corrupted (true = file corrupted)."
+ },
"URLCLASSIFIER_LC_PREFIXES": {
"alert_emails": ["safebrowsing-telemetry@mozilla.org"],
"expires_in_version": "never",
@@ -3867,7 +3874,7 @@
"kind": "enumerated",
"n_values": 10,
"bug_numbers": [1305801],
- "description": "An error was encountered while parsing a partial update returned by a Safe Browsing V4 server (0 = addition of an already existing prefix, 1 = parser got into an infinite loop, 2 = removal index out of bounds)"
+ "description": "An error was encountered while parsing a partial update returned by a Safe Browsing V4 server (0 = addition of an already existing prefix, 1 = parser got into an infinite loop, 2 = removal index out of bounds, 3 = checksum mismatch, 4 = missing checksum)"
},
"CSP_DOCUMENTS_COUNT": {
"alert_emails": ["seceng@mozilla.com"],
diff --git a/toolkit/components/telemetry/TelemetrySend.jsm b/toolkit/components/telemetry/TelemetrySend.jsm
index 68f3cb3c3f84..9e8cd9aec308 100644
--- a/toolkit/components/telemetry/TelemetrySend.jsm
+++ b/toolkit/components/telemetry/TelemetrySend.jsm
@@ -907,6 +907,9 @@ var TelemetrySendImpl = {
this._pendingPingRequests.set(id, request);
+ // Prevent the request channel from running though URLClassifier (bug 1296802)
+ request.channel.loadFlags &= ~Ci.nsIChannel.LOAD_CLASSIFY_URI;
+
let startTime = new Date();
let deferred = PromiseUtils.defer();
diff --git a/toolkit/components/url-classifier/Classifier.cpp b/toolkit/components/url-classifier/Classifier.cpp
index 9aaac3de43d7..930ebd4ece77 100644
--- a/toolkit/components/url-classifier/Classifier.cpp
+++ b/toolkit/components/url-classifier/Classifier.cpp
@@ -983,11 +983,11 @@ Classifier::UpdateTableV4(nsTArray* aUpdates,
nsresult rv = NS_OK;
- // prefixes2 is only used in partial update. If there are multiple
- // updates for the same table, prefixes1 & prefixes2 will act as
- // input and output in turn to reduce memory copy overhead.
+ // If there are multiple updates for the same table, prefixes1 & prefixes2
+ // will act as input and output in turn to reduce memory copy overhead.
PrefixStringMap prefixes1, prefixes2;
- PrefixStringMap* output = &prefixes1;
+ PrefixStringMap* input = &prefixes1;
+ PrefixStringMap* output = &prefixes2;
TableUpdateV4* lastAppliedUpdate = nullptr;
for (uint32_t i = 0; i < aUpdates->Length(); i++) {
@@ -1000,24 +1000,18 @@ Classifier::UpdateTableV4(nsTArray* aUpdates,
NS_ENSURE_TRUE(updateV4, NS_ERROR_FAILURE);
if (updateV4->IsFullUpdate()) {
- TableUpdateV4::PrefixStdStringMap& map = updateV4->Prefixes();
-
+ input->Clear();
output->Clear();
- for (auto iter = map.Iter(); !iter.Done(); iter.Next()) {
- // prefixes is an nsClassHashtable object stores prefix string.
- // It will take the ownership of the put object.
- nsCString* prefix = new nsCString(iter.Data()->GetPrefixString());
- output->Put(iter.Key(), prefix);
+ rv = lookupCache->ApplyUpdate(updateV4, *input, *output);
+ if (NS_FAILED(rv)) {
+ return rv;
}
} else {
- PrefixStringMap* input = nullptr;
// If both prefix sets are empty, this means we are doing a partial update
// without a prior full/partial update in the loop. In this case we should
// get prefixes from the lookup cache first.
if (prefixes1.IsEmpty() && prefixes2.IsEmpty()) {
lookupCache->GetPrefixes(prefixes1);
- input = &prefixes1;
- output = &prefixes2;
} else {
MOZ_ASSERT(prefixes1.IsEmpty() ^ prefixes2.IsEmpty());
@@ -1028,8 +1022,10 @@ Classifier::UpdateTableV4(nsTArray* aUpdates,
output = prefixes1.IsEmpty() ? &prefixes1 : &prefixes2;
}
- rv = lookupCache->ApplyPartialUpdate(updateV4, *input, *output);
- NS_ENSURE_SUCCESS(rv, rv);
+ rv = lookupCache->ApplyUpdate(updateV4, *input, *output);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
input->Clear();
}
diff --git a/toolkit/components/url-classifier/LookupCacheV4.cpp b/toolkit/components/url-classifier/LookupCacheV4.cpp
index 38752dd1e0ce..82e22d030d7c 100644
--- a/toolkit/components/url-classifier/LookupCacheV4.cpp
+++ b/toolkit/components/url-classifier/LookupCacheV4.cpp
@@ -5,6 +5,8 @@
#include "LookupCacheV4.h"
#include "HashStore.h"
+#include "mozilla/Unused.h"
+#include
// MOZ_LOG=UrlClassifierDbService:5
extern mozilla::LazyLogModule gUrlClassifierDbServiceLog;
@@ -114,7 +116,22 @@ LookupCacheV4::StoreToFile(nsIFile* aFile)
nsresult
LookupCacheV4::LoadFromFile(nsIFile* aFile)
{
- return mVLPrefixSet->LoadFromFile(aFile);
+ nsresult rv = mVLPrefixSet->LoadFromFile(aFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCString state, checksum;
+ rv = LoadMetadata(state, checksum);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = VerifyChecksum(checksum);
+ Telemetry::Accumulate(Telemetry::URLCLASSIFIER_VLPS_LOAD_CORRUPT,
+ rv == NS_ERROR_FILE_CORRUPTED);
+
+ return rv;
}
size_t
@@ -137,12 +154,18 @@ AppendPrefixToMap(PrefixStringMap& prefixes, nsDependentCSubstring& prefix)
// Please see https://bug1287058.bmoattachments.org/attachment.cgi?id=8795366
// for detail about partial update algorithm.
nsresult
-LookupCacheV4::ApplyPartialUpdate(TableUpdateV4* aTableUpdate,
- PrefixStringMap& aInputMap,
- PrefixStringMap& aOutputMap)
+LookupCacheV4::ApplyUpdate(TableUpdateV4* aTableUpdate,
+ PrefixStringMap& aInputMap,
+ PrefixStringMap& aOutputMap)
{
MOZ_ASSERT(aOutputMap.IsEmpty());
+ nsCOMPtr crypto;
+ nsresult rv = InitCrypto(crypto);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
// oldPSet contains prefixes we already have or we just merged last round.
// addPSet contains prefixes stored in tableUpdate which should be merged with oldPSet.
VLPrefixSet oldPSet(aInputMap);
@@ -159,40 +182,47 @@ LookupCacheV4::ApplyPartialUpdate(TableUpdateV4* aTableUpdate,
nsDependentCSubstring smallestOldPrefix;
nsDependentCSubstring smallestAddPrefix;
+ bool isOldMapEmpty = false, isAddMapEmpty = false;
+
// This is used to avoid infinite loop for partial update algorithm.
// The maximum loops will be the number of old prefixes plus the number of add prefixes.
- uint32_t index = oldPSet.Count() + addPSet.Count() + 1;
+ int32_t index = oldPSet.Count() + addPSet.Count() + 1;
for(;index > 0; index--) {
// Get smallest prefix from the old prefix set if we don't have one
- if (smallestOldPrefix.IsEmpty()) {
- // If prefixes from the old prefix set are all merged,
- // then we can merge the entire add prefix set directly.
- if (!oldPSet.GetSmallestPrefix(smallestOldPrefix)) {
- AppendPrefixToMap(aOutputMap, smallestAddPrefix);
- addPSet.Merge(aOutputMap);
- break;
- }
+ if (smallestOldPrefix.IsEmpty() && !isOldMapEmpty) {
+ isOldMapEmpty = !oldPSet.GetSmallestPrefix(smallestOldPrefix);
}
// Get smallest prefix from add prefix set if we don't have one
- if (smallestAddPrefix.IsEmpty()) {
- // If add prefixes are all merged and there is no removalIndices left,
- // then merge the entire old prefix set directly. If there are still
- // removalIndices left, we should still merge prefixes one by one
- // to know which prefix from old prefix set should be removed.
- if (!addPSet.GetSmallestPrefix(smallestAddPrefix) &&
- removalIndex >= removalArray.Length()) {
- AppendPrefixToMap(aOutputMap, smallestOldPrefix);
- oldPSet.Merge(aOutputMap);
- break;
- }
+ if (smallestAddPrefix.IsEmpty() && !isAddMapEmpty) {
+ isAddMapEmpty = !addPSet.GetSmallestPrefix(smallestAddPrefix);
}
- // Compare the smallest string in old prefix set and add prefix set, merge the
- // smaller one into new map to ensure merged string still follows
- // lexigraphic order.
- if (smallestOldPrefix < smallestAddPrefix ||
- smallestAddPrefix.IsEmpty()) {
+ bool pickOld;
+
+ // If both prefix sets are not empty, then compare to find the smaller one.
+ if (!isOldMapEmpty && !isAddMapEmpty) {
+ if (smallestOldPrefix == smallestAddPrefix) {
+ LOG(("Add prefix should not exist in the original prefix set."));
+ Telemetry::Accumulate(Telemetry::URLCLASSIFIER_UPDATE_ERROR_TYPE,
+ DUPLICATE_PREFIX);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Compare the smallest string in old prefix set and add prefix set,
+ // merge the smaller one into new map to ensure merged string still
+ // follows lexigraphic order.
+ pickOld = smallestOldPrefix < smallestAddPrefix;
+ } else if (!isOldMapEmpty && isAddMapEmpty) {
+ pickOld = true;
+ } else if (isOldMapEmpty && !isAddMapEmpty) {
+ pickOld = false;
+ // If both maps are empty, then partial update is complete.
+ } else {
+ break;
+ }
+
+ if (pickOld) {
numOldPrefixPicked++;
// If the number of picks from old map matches the removalIndex, then this prefix
@@ -202,36 +232,108 @@ LookupCacheV4::ApplyPartialUpdate(TableUpdateV4* aTableUpdate,
removalIndex++;
} else {
AppendPrefixToMap(aOutputMap, smallestOldPrefix);
+
+ crypto->Update(reinterpret_cast(const_cast(
+ smallestOldPrefix.BeginReading())),
+ smallestOldPrefix.Length());
}
smallestOldPrefix.SetLength(0);
- } else if (smallestOldPrefix > smallestAddPrefix ||
- smallestOldPrefix.IsEmpty()){
- AppendPrefixToMap(aOutputMap, smallestAddPrefix);
- smallestAddPrefix.SetLength(0);
} else {
- NS_WARNING("Add prefix should not exist in the original prefix set.");
- Telemetry::Accumulate(Telemetry::URLCLASSIFIER_UPDATE_ERROR_TYPE,
- DUPLICATE_PREFIX);
- return NS_ERROR_FAILURE;
+ AppendPrefixToMap(aOutputMap, smallestAddPrefix);
+
+ crypto->Update(reinterpret_cast(const_cast(
+ smallestAddPrefix.BeginReading())),
+ smallestAddPrefix.Length());
+
+ smallestAddPrefix.SetLength(0);
}
}
// We expect index will be greater to 0 because max number of runs will be
// the number of original prefix plus add prefix.
if (index <= 0) {
- NS_WARNING("There are still prefixes remaining after reaching maximum runs.");
+ LOG(("There are still prefixes remaining after reaching maximum runs."));
Telemetry::Accumulate(Telemetry::URLCLASSIFIER_UPDATE_ERROR_TYPE,
INFINITE_LOOP);
return NS_ERROR_FAILURE;
}
if (removalIndex < removalArray.Length()) {
- NS_WARNING("There are still prefixes to remove after exhausting the old PrefixSet.");
+ LOG(("There are still prefixes to remove after exhausting the old PrefixSet."));
Telemetry::Accumulate(Telemetry::URLCLASSIFIER_UPDATE_ERROR_TYPE,
WRONG_REMOVAL_INDICES);
return NS_ERROR_FAILURE;
}
+ nsAutoCString checksum;
+ crypto->Finish(false, checksum);
+ if (aTableUpdate->Checksum().IsEmpty()) {
+ LOG(("Update checksum missing."));
+ Telemetry::Accumulate(Telemetry::URLCLASSIFIER_UPDATE_ERROR_TYPE,
+ MISSING_CHECKSUM);
+
+ // Generate our own checksum to tableUpdate to ensure there is always
+ // checksum in .metadata
+ std::string stdChecksum(checksum.BeginReading(), checksum.Length());
+ aTableUpdate->NewChecksum(stdChecksum);
+
+ } else if (aTableUpdate->Checksum() != checksum){
+ LOG(("Checksum mismatch after applying partial update"));
+ Telemetry::Accumulate(Telemetry::URLCLASSIFIER_UPDATE_ERROR_TYPE,
+ CHECKSUM_MISMATCH);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+LookupCacheV4::InitCrypto(nsCOMPtr& aCrypto)
+{
+ nsresult rv;
+ aCrypto = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aCrypto->Init(nsICryptoHash::SHA256);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ return rv;
+}
+
+nsresult
+LookupCacheV4::VerifyChecksum(const nsACString& aChecksum)
+{
+ nsCOMPtr crypto;
+ nsresult rv = InitCrypto(crypto);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ PrefixStringMap map;
+ mVLPrefixSet->GetPrefixes(map);
+
+ VLPrefixSet loadPSet(map);
+ uint32_t index = loadPSet.Count() + 1;
+ for(;index > 0; index--) {
+ nsDependentCSubstring prefix;
+ if (!loadPSet.GetSmallestPrefix(prefix)) {
+ break;
+ }
+ crypto->Update(reinterpret_cast(const_cast(
+ prefix.BeginReading())),
+ prefix.Length());
+ }
+
+ nsAutoCString checksum;
+ crypto->Finish(false, checksum);
+
+ if (checksum != aChecksum) {
+ LOG(("Checksum mismatch when loading prefixes from file."));
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
return NS_OK;
}
diff --git a/toolkit/components/url-classifier/LookupCacheV4.h b/toolkit/components/url-classifier/LookupCacheV4.h
index 28ac7155b3fb..f2af1b64bcd2 100644
--- a/toolkit/components/url-classifier/LookupCacheV4.h
+++ b/toolkit/components/url-classifier/LookupCacheV4.h
@@ -29,11 +29,10 @@ public:
nsresult GetPrefixes(PrefixStringMap& aPrefixMap);
- // ApplyPartialUpdate will merge partial update data stored in aTableUpdate
- // with prefixes in aInputMap.
- nsresult ApplyPartialUpdate(TableUpdateV4* aTableUpdate,
- PrefixStringMap& aInputMap,
- PrefixStringMap& aOutputMap);
+ // ApplyUpdate will merge data stored in aTableUpdate with prefixes in aInputMap.
+ nsresult ApplyUpdate(TableUpdateV4* aTableUpdate,
+ PrefixStringMap& aInputMap,
+ PrefixStringMap& aOutputMap);
nsresult WriteMetadata(TableUpdateV4* aTableUpdate);
nsresult LoadMetadata(nsACString& aState, nsACString& aChecksum);
@@ -49,10 +48,15 @@ protected:
private:
virtual int Ver() const override { return VER; }
+ nsresult InitCrypto(nsCOMPtr& aCrypto);
+ nsresult VerifyChecksum(const nsACString& aChecksum);
+
enum UPDATE_ERROR_TYPES {
DUPLICATE_PREFIX = 0,
INFINITE_LOOP = 1,
WRONG_REMOVAL_INDICES = 2,
+ CHECKSUM_MISMATCH = 3,
+ MISSING_CHECKSUM = 4,
};
RefPtr mVLPrefixSet;
diff --git a/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierTableUpdateV4.cpp b/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierTableUpdateV4.cpp
index f1749dbb90c6..6736e3b1ecfe 100644
--- a/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierTableUpdateV4.cpp
+++ b/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierTableUpdateV4.cpp
@@ -1,3 +1,6 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
#include "Classifier.h"
#include "HashStore.h"
#include "nsAppDirectoryServiceDefs.h"
@@ -13,6 +16,10 @@ using namespace mozilla::safebrowsing;
typedef nsCString _Prefix;
typedef nsTArray<_Prefix> _PrefixArray;
+#define GTEST_SAFEBROWSING_DIR NS_LITERAL_CSTRING("safebrowsing")
+#define GTEST_TABLE NS_LITERAL_CSTRING("gtest-malware-proto")
+#define GTEST_PREFIXFILE NS_LITERAL_CSTRING("gtest-malware-proto.pset")
+
// This function removes common elements of inArray and outArray from
// outArray. This is used by partial update testcase to ensure partial update
// data won't contain prefixes we already have.
@@ -37,6 +44,17 @@ RemoveElements(const nsTArray& removal, _PrefixArray& outArray)
}
}
+static void
+MergeAndSortArray(const _PrefixArray& array1,
+ const _PrefixArray& array2,
+ _PrefixArray& output)
+{
+ output.Clear();
+ output.AppendElements(array1);
+ output.AppendElements(array2);
+ output.Sort();
+}
+
// This function converts lexigraphic-sorted prefixes to a hashtable
// which key is prefix size and value is concatenated prefix string.
static void
@@ -52,6 +70,24 @@ PrefixArrayToPrefixStringMap(const _PrefixArray& prefixArray,
}
}
+static void
+CalculateCheckSum(_PrefixArray& prefixArray, nsCString& checksum)
+{
+ prefixArray.Sort();
+
+ nsresult rv;
+ nsCOMPtr cryptoHash =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+
+ cryptoHash->Init(nsICryptoHash::SHA256);
+ for (uint32_t i = 0; i < prefixArray.Length(); i++) {
+ const _Prefix& prefix = prefixArray[i];
+ cryptoHash->Update(reinterpret_cast(
+ const_cast(prefix.get())), prefix.Length());
+ }
+ cryptoHash->Finish(false, checksum);
+}
+
// N: Number of prefixes, MIN/MAX: minimum/maximum prefix size
// This function will append generated prefixes to outArray.
static void
@@ -103,10 +139,11 @@ CreateRandomRemovalIndices(uint32_t N,
static void
GenerateUpdateData(bool fullUpdate,
PrefixStringMap& add,
- nsTArray& removal,
+ nsTArray* removal,
+ nsCString* checksum,
nsTArray& tableUpdates)
{
- TableUpdateV4* tableUpdate = new TableUpdateV4(NS_LITERAL_CSTRING("gtest-malware-proto"));
+ TableUpdateV4* tableUpdate = new TableUpdateV4(GTEST_TABLE);
tableUpdate->SetFullUpdate(fullUpdate);
for (auto iter = add.ConstIter(); !iter.Done(); iter.Next()) {
@@ -116,7 +153,16 @@ GenerateUpdateData(bool fullUpdate,
tableUpdate->NewPrefixes(iter.Key(), str);
}
- tableUpdate->NewRemovalIndices(removal.Elements(), removal.Length());
+ if (removal) {
+ tableUpdate->NewRemovalIndices(removal->Elements(), removal->Length());
+ }
+
+ if (checksum) {
+ std::string stdChecksum;
+ stdChecksum.assign(const_cast(checksum->BeginReading()), checksum->Length());
+
+ tableUpdate->NewChecksum(stdChecksum);
+ }
tableUpdates.AppendElement(tableUpdate);
}
@@ -126,14 +172,13 @@ VerifyPrefixSet(PrefixStringMap& expected)
{
// Verify the prefix set is written to disk.
nsCOMPtr file;
- NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
- getter_AddRefs(file));
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
- file->AppendNative(NS_LITERAL_CSTRING("safebrowsing"));
- file->AppendNative(NS_LITERAL_CSTRING("gtest-malware-proto.pset"));
+ file->AppendNative(GTEST_SAFEBROWSING_DIR);
+ file->AppendNative(GTEST_PREFIXFILE);
RefPtr load = new VariableLengthPrefixSet;
- load->Init(NS_LITERAL_CSTRING("gtest-malware-proto"));
+ load->Init(GTEST_TABLE);
PrefixStringMap prefixesInFile;
load->LoadFromFile(file);
@@ -151,8 +196,7 @@ static void
Clear()
{
nsCOMPtr file;
- NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
- getter_AddRefs(file));
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
UniquePtr classifier(new Classifier());
classifier->Open(*file);
@@ -163,8 +207,7 @@ static void
testUpdateFail(nsTArray& tableUpdates)
{
nsCOMPtr file;
- NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
- getter_AddRefs(file));
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
UniquePtr classifier(new Classifier());
classifier->Open(*file);
@@ -180,8 +223,7 @@ testUpdate(nsTArray& tableUpdates,
PrefixStringMap& expected)
{
nsCOMPtr file;
- NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
- getter_AddRefs(file));
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
UniquePtr classifier(new Classifier());
classifier->Open(*file);
@@ -195,52 +237,73 @@ testUpdate(nsTArray& tableUpdates,
}
static void
-testFullUpdate(PrefixStringMap& add)
+testFullUpdate(PrefixStringMap& add, nsCString* checksum)
{
- nsTArray empty;
nsTArray tableUpdates;
- GenerateUpdateData(true, add, empty, tableUpdates);
+
+ GenerateUpdateData(true, add, nullptr, checksum, tableUpdates);
testUpdate(tableUpdates, add);
}
static void
testPartialUpdate(PrefixStringMap& add,
- nsTArray& removal,
+ nsTArray* removal,
+ nsCString* checksum,
PrefixStringMap& expected)
{
nsTArray tableUpdates;
- GenerateUpdateData(false, add, removal, tableUpdates);
+ GenerateUpdateData(false, add, removal, checksum, tableUpdates);
testUpdate(tableUpdates, expected);
}
+static void
+testOpenLookupCache()
+{
+ nsCOMPtr file;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
+ file->AppendNative(GTEST_SAFEBROWSING_DIR);
+ RunTestInNewThread([&] () -> void {
+ LookupCacheV4 cache(nsCString(GTEST_TABLE), file);
+ nsresult rv = cache.Init();
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = cache.Open();
+ ASSERT_EQ(rv, NS_OK);
+ });
+}
+
+// Tests start from here.
TEST(UrlClassifierTableUpdateV4, FixLenghtPSetFullUpdate)
{
srand(time(NULL));
_PrefixArray array;
PrefixStringMap map;
+ nsCString checksum;
CreateRandomSortedPrefixArray(5000, 4, 4, array);
PrefixArrayToPrefixStringMap(array, map);
+ CalculateCheckSum(array, checksum);
- testFullUpdate(map);
+ testFullUpdate(map, &checksum);
Clear();
}
-
TEST(UrlClassifierTableUpdateV4, VariableLenghtPSetFullUpdate)
{
_PrefixArray array;
PrefixStringMap map;
+ nsCString checksum;
CreateRandomSortedPrefixArray(5000, 5, 32, array);
PrefixArrayToPrefixStringMap(array, map);
+ CalculateCheckSum(array, checksum);
- testFullUpdate(map);
+ testFullUpdate(map, &checksum);
Clear();
}
@@ -250,30 +313,41 @@ TEST(UrlClassifierTableUpdateV4, MixedPSetFullUpdate)
{
_PrefixArray array;
PrefixStringMap map;
+ nsCString checksum;
CreateRandomSortedPrefixArray(5000, 4, 4, array);
CreateRandomSortedPrefixArray(1000, 5, 32, array);
PrefixArrayToPrefixStringMap(array, map);
+ CalculateCheckSum(array, checksum);
- testFullUpdate(map);
+ testFullUpdate(map, &checksum);
Clear();
}
TEST(UrlClassifierTableUpdateV4, PartialUpdateWithRemoval)
{
- _PrefixArray fArray, pArray, mergedArray;
- PrefixStringMap fMap, pMap, mergedMap;
+ _PrefixArray fArray;
+ // Apply a full update first.
{
+ PrefixStringMap fMap;
+ nsCString checksum;
+
CreateRandomSortedPrefixArray(10000, 4, 4, fArray);
CreateRandomSortedPrefixArray(2000, 5, 32, fArray);
PrefixArrayToPrefixStringMap(fArray, fMap);
+ CalculateCheckSum(fArray, checksum);
- testFullUpdate(fMap);
+ testFullUpdate(fMap, &checksum);
}
+ // Apply a partial update with removal.
{
+ _PrefixArray pArray, mergedArray;
+ PrefixStringMap pMap, mergedMap;
+ nsCString checksum;
+
CreateRandomSortedPrefixArray(5000, 4, 4, pArray);
CreateRandomSortedPrefixArray(1000, 5, 32, pArray);
RemoveIntersection(fArray, pArray);
@@ -285,12 +359,11 @@ TEST(UrlClassifierTableUpdateV4, PartialUpdateWithRemoval)
RemoveElements(removal, fArray);
// Calculate the expected prefix map.
- mergedArray.AppendElements(fArray);
- mergedArray.AppendElements(pArray);
- mergedArray.Sort();
+ MergeAndSortArray(fArray, pArray, mergedArray);
PrefixArrayToPrefixStringMap(mergedArray, mergedMap);
+ CalculateCheckSum(mergedArray, checksum);
- testPartialUpdate(pMap, removal, mergedMap);
+ testPartialUpdate(pMap, &removal, &checksum, mergedMap);
}
Clear();
@@ -298,19 +371,26 @@ TEST(UrlClassifierTableUpdateV4, PartialUpdateWithRemoval)
TEST(UrlClassifierTableUpdateV4, PartialUpdateWithoutRemoval)
{
- _PrefixArray fArray, pArray, mergedArray;
- PrefixStringMap fMap, pMap, mergedMap;
+ _PrefixArray fArray;
+ // Apply a full update first.
{
+ PrefixStringMap fMap;
+ nsCString checksum;
+
CreateRandomSortedPrefixArray(10000, 4, 4, fArray);
CreateRandomSortedPrefixArray(2000, 5, 32, fArray);
PrefixArrayToPrefixStringMap(fArray, fMap);
+ CalculateCheckSum(fArray, checksum);
- testFullUpdate(fMap);
+ testFullUpdate(fMap, &checksum);
}
+ // Apply a partial update without removal
{
- nsTArray empty;
+ _PrefixArray pArray, mergedArray;
+ PrefixStringMap pMap, mergedMap;
+ nsCString checksum;
CreateRandomSortedPrefixArray(5000, 4, 4, pArray);
CreateRandomSortedPrefixArray(1000, 5, 32, pArray);
@@ -318,12 +398,11 @@ TEST(UrlClassifierTableUpdateV4, PartialUpdateWithoutRemoval)
PrefixArrayToPrefixStringMap(pArray, pMap);
// Calculate the expected prefix map.
- mergedArray.AppendElements(fArray);
- mergedArray.AppendElements(pArray);
- mergedArray.Sort();
+ MergeAndSortArray(fArray, pArray, mergedArray);
PrefixArrayToPrefixStringMap(mergedArray, mergedMap);
+ CalculateCheckSum(mergedArray, checksum);
- testPartialUpdate(pMap, empty, mergedMap);
+ testPartialUpdate(pMap, nullptr, &checksum, mergedMap);
}
Clear();
@@ -333,18 +412,25 @@ TEST(UrlClassifierTableUpdateV4, PartialUpdateWithoutRemoval)
// in old prefix set.
TEST(UrlClassifierTableUpdateV4, PartialUpdatePrefixAlreadyExist)
{
- _PrefixArray fArray, pArray;
- PrefixStringMap fMap, pMap;
+ _PrefixArray fArray;
+ // Apply a full update fist.
{
+ PrefixStringMap fMap;
+ nsCString checksum;
+
CreateRandomSortedPrefixArray(1000, 4, 32, fArray);
PrefixArrayToPrefixStringMap(fArray, fMap);
+ CalculateCheckSum(fArray, checksum);
- testFullUpdate(fMap);
+ testFullUpdate(fMap, &checksum);
}
+ // Apply a partial update which contains a prefix in previous full update.
+ // This should cause an update error.
{
- nsTArray empty;
+ _PrefixArray pArray;
+ PrefixStringMap pMap;
nsTArray tableUpdates;
// Pick one prefix from full update prefix and add it to partial update.
@@ -353,7 +439,7 @@ TEST(UrlClassifierTableUpdateV4, PartialUpdatePrefixAlreadyExist)
CreateRandomSortedPrefixArray(200, 4, 32, pArray);
PrefixArrayToPrefixStringMap(pArray, pMap);
- GenerateUpdateData(false, pMap, empty, tableUpdates);
+ GenerateUpdateData(false, pMap, nullptr, nullptr, tableUpdates);
testUpdateFail(tableUpdates);
}
@@ -365,13 +451,14 @@ TEST(UrlClassifierTableUpdateV4, OnlyPartialUpdate)
{
_PrefixArray pArray;
PrefixStringMap pMap;
- nsTArray empty;
+ nsCString checksum;
CreateRandomSortedPrefixArray(5000, 4, 4, pArray);
CreateRandomSortedPrefixArray(1000, 5, 32, pArray);
PrefixArrayToPrefixStringMap(pArray, pMap);
+ CalculateCheckSum(pArray, checksum);
- testPartialUpdate(pMap, empty, pMap);
+ testPartialUpdate(pMap, nullptr, &checksum, pMap);
Clear();
}
@@ -379,26 +466,36 @@ TEST(UrlClassifierTableUpdateV4, OnlyPartialUpdate)
// Test partial update without any ADD prefixes, only removalIndices.
TEST(UrlClassifierTableUpdateV4, PartialUpdateOnlyRemoval)
{
- _PrefixArray fArray, pArray;
- PrefixStringMap fMap, pMap, mergedMap;
+ _PrefixArray fArray;
+ // Apply a full update first.
{
+ PrefixStringMap fMap;
+ nsCString checksum;
+
CreateRandomSortedPrefixArray(5000, 4, 4, fArray);
CreateRandomSortedPrefixArray(1000, 5, 32, fArray);
PrefixArrayToPrefixStringMap(fArray, fMap);
+ CalculateCheckSum(fArray, checksum);
- testFullUpdate(fMap);
+ testFullUpdate(fMap, &checksum);
}
+ // Apply a partial update without add prefix, only contain removal indices.
{
+ _PrefixArray pArray;
+ PrefixStringMap pMap, mergedMap;
+ nsCString checksum;
+
// Remove 1/5 of elements of original prefix set.
nsTArray removal;
CreateRandomRemovalIndices(fArray.Length() / 5, fArray.Length(), removal);
RemoveElements(removal, fArray);
PrefixArrayToPrefixStringMap(fArray, mergedMap);
+ CalculateCheckSum(fArray, checksum);
- testPartialUpdate(pMap, removal, mergedMap);
+ testPartialUpdate(pMap, &removal, &checksum, mergedMap);
}
Clear();
@@ -409,49 +506,50 @@ TEST(UrlClassifierTableUpdateV4, MultipleTableUpdates)
{
_PrefixArray fArray, pArray, mergedArray;
PrefixStringMap fMap, pMap, mergedMap;
+ nsCString checksum;
- {
- nsTArray empty;
- nsTArray tableUpdates;
+ nsTArray tableUpdates;
- // Generate first full udpate
- CreateRandomSortedPrefixArray(10000, 4, 4, fArray);
- CreateRandomSortedPrefixArray(2000, 5, 32, fArray);
- PrefixArrayToPrefixStringMap(fArray, fMap);
+ // Generate first full udpate
+ CreateRandomSortedPrefixArray(10000, 4, 4, fArray);
+ CreateRandomSortedPrefixArray(2000, 5, 32, fArray);
+ PrefixArrayToPrefixStringMap(fArray, fMap);
+ CalculateCheckSum(fArray, checksum);
- GenerateUpdateData(true, fMap, empty, tableUpdates);
+ GenerateUpdateData(true, fMap, nullptr, &checksum, tableUpdates);
- // Generate second partial update
- CreateRandomSortedPrefixArray(3000, 4, 4, pArray);
- CreateRandomSortedPrefixArray(1000, 5, 32, pArray);
- RemoveIntersection(fArray, pArray);
- PrefixArrayToPrefixStringMap(pArray, pMap);
+ // Generate second partial update
+ CreateRandomSortedPrefixArray(3000, 4, 4, pArray);
+ CreateRandomSortedPrefixArray(1000, 5, 32, pArray);
+ RemoveIntersection(fArray, pArray);
+ PrefixArrayToPrefixStringMap(pArray, pMap);
- GenerateUpdateData(false, pMap, empty, tableUpdates);
+ MergeAndSortArray(fArray, pArray, mergedArray);
+ CalculateCheckSum(mergedArray, checksum);
- // Generate thrid partial update
- fArray.AppendElements(pArray);
- fArray.Sort();
- pArray.Clear();
- CreateRandomSortedPrefixArray(3000, 4, 4, pArray);
- CreateRandomSortedPrefixArray(1000, 5, 32, pArray);
- RemoveIntersection(fArray, pArray);
- PrefixArrayToPrefixStringMap(pArray, pMap);
+ GenerateUpdateData(false, pMap, nullptr, &checksum, tableUpdates);
- // Remove 1/5 of elements of original prefix set.
- nsTArray removal;
- CreateRandomRemovalIndices(fArray.Length() / 5, fArray.Length(), removal);
- RemoveElements(removal, fArray);
+ // Generate thrid partial update
+ fArray.AppendElements(pArray);
+ fArray.Sort();
+ pArray.Clear();
+ CreateRandomSortedPrefixArray(3000, 4, 4, pArray);
+ CreateRandomSortedPrefixArray(1000, 5, 32, pArray);
+ RemoveIntersection(fArray, pArray);
+ PrefixArrayToPrefixStringMap(pArray, pMap);
- GenerateUpdateData(false, pMap, removal, tableUpdates);
+ // Remove 1/5 of elements of original prefix set.
+ nsTArray removal;
+ CreateRandomRemovalIndices(fArray.Length() / 5, fArray.Length(), removal);
+ RemoveElements(removal, fArray);
- mergedArray.AppendElements(fArray);
- mergedArray.AppendElements(pArray);
- mergedArray.Sort();
- PrefixArrayToPrefixStringMap(mergedArray, mergedMap);
+ MergeAndSortArray(fArray, pArray, mergedArray);
+ PrefixArrayToPrefixStringMap(mergedArray, mergedMap);
+ CalculateCheckSum(mergedArray, checksum);
- testUpdate(tableUpdates, mergedMap);
- }
+ GenerateUpdateData(false, pMap, &removal, &checksum, tableUpdates);
+
+ testUpdate(tableUpdates, mergedMap);
Clear();
}
@@ -460,19 +558,27 @@ TEST(UrlClassifierTableUpdateV4, MultipleTableUpdates)
// in one tableupdate array.
TEST(UrlClassifierTableUpdateV4, MultiplePartialUpdateTableUpdates)
{
- _PrefixArray fArray, pArray, mergedArray;
- PrefixStringMap fMap, pMap, mergedMap;
+ _PrefixArray fArray;
+ // Apply a full update first
{
+ PrefixStringMap fMap;
+ nsCString checksum;
+
// Generate first full udpate
CreateRandomSortedPrefixArray(10000, 4, 4, fArray);
CreateRandomSortedPrefixArray(3000, 5, 32, fArray);
PrefixArrayToPrefixStringMap(fArray, fMap);
+ CalculateCheckSum(fArray, checksum);
- testFullUpdate(fMap);
+ testFullUpdate(fMap, &checksum);
}
+ // Apply multiple partial updates in one table update
{
+ _PrefixArray pArray, mergedArray;
+ PrefixStringMap pMap, mergedMap;
+ nsCString checksum;
nsTArray removal;
nsTArray tableUpdates;
@@ -486,7 +592,10 @@ TEST(UrlClassifierTableUpdateV4, MultiplePartialUpdateTableUpdates)
CreateRandomRemovalIndices(fArray.Length() / 5, fArray.Length(), removal);
RemoveElements(removal, fArray);
- GenerateUpdateData(false, pMap, removal, tableUpdates);
+ MergeAndSortArray(fArray, pArray, mergedArray);
+ CalculateCheckSum(mergedArray, checksum);
+
+ GenerateUpdateData(false, pMap, &removal, &checksum, tableUpdates);
fArray.AppendElements(pArray);
fArray.Sort();
@@ -503,12 +612,11 @@ TEST(UrlClassifierTableUpdateV4, MultiplePartialUpdateTableUpdates)
CreateRandomRemovalIndices(fArray.Length() / 5, fArray.Length(), removal);
RemoveElements(removal, fArray);
- GenerateUpdateData(false, pMap, removal, tableUpdates);
-
- mergedArray.AppendElements(fArray);
- mergedArray.AppendElements(pArray);
- mergedArray.Sort();
+ MergeAndSortArray(fArray, pArray, mergedArray);
PrefixArrayToPrefixStringMap(mergedArray, mergedMap);
+ CalculateCheckSum(mergedArray, checksum);
+
+ GenerateUpdateData(false, pMap, &removal, &checksum, tableUpdates);
testUpdate(tableUpdates, mergedMap);
}
@@ -519,17 +627,25 @@ TEST(UrlClassifierTableUpdateV4, MultiplePartialUpdateTableUpdates)
// Test removal indices are larger than the original prefix set.
TEST(UrlClassifierTableUpdateV4, RemovalIndexTooLarge)
{
- _PrefixArray fArray, pArray;
- PrefixStringMap fMap, pMap;
+ _PrefixArray fArray;
+ // Apply a full update first
{
+ PrefixStringMap fMap;
+ nsCString checksum;
+
CreateRandomSortedPrefixArray(1000, 4, 32, fArray);
PrefixArrayToPrefixStringMap(fArray, fMap);
+ CalculateCheckSum(fArray, checksum);
- testFullUpdate(fMap);
+ testFullUpdate(fMap, &checksum);
}
+ // Apply a partial update with removal indice array larger than
+ // old prefix set(fArray). This should cause an error.
{
+ _PrefixArray pArray;
+ PrefixStringMap pMap;
nsTArray removal;
nsTArray tableUpdates;
@@ -541,9 +657,113 @@ TEST(UrlClassifierTableUpdateV4, RemovalIndexTooLarge)
removal.AppendElement(i);
}
- GenerateUpdateData(false, pMap, removal, tableUpdates);
+ GenerateUpdateData(false, pMap, &removal, nullptr, tableUpdates);
testUpdateFail(tableUpdates);
}
Clear();
}
+
+TEST(UrlClassifierTableUpdateV4, ChecksumMismatch)
+{
+ // Apply a full update first
+ {
+ _PrefixArray fArray;
+ PrefixStringMap fMap;
+ nsCString checksum;
+
+ CreateRandomSortedPrefixArray(1000, 4, 32, fArray);
+ PrefixArrayToPrefixStringMap(fArray, fMap);
+ CalculateCheckSum(fArray, checksum);
+
+ testFullUpdate(fMap, &checksum);
+ }
+
+ // Apply a partial update with incorrect checksum
+ {
+ _PrefixArray pArray;
+ PrefixStringMap pMap;
+ nsCString checksum;
+ nsTArray tableUpdates;
+
+ CreateRandomSortedPrefixArray(200, 4, 32, pArray);
+ PrefixArrayToPrefixStringMap(pArray, pMap);
+
+ // Checksum should be calculated with both old prefix set and add prefix set,
+ // here we only calculate checksum with add prefix set to check if applyUpdate
+ // will return failure.
+ CalculateCheckSum(pArray, checksum);
+
+ GenerateUpdateData(false, pMap, nullptr, &checksum, tableUpdates);
+ testUpdateFail(tableUpdates);
+ }
+
+ Clear();
+}
+
+TEST(UrlClassifierTableUpdateV4, ApplyUpdateThenLoad)
+{
+ // Apply update with checksum
+ {
+ _PrefixArray fArray;
+ PrefixStringMap fMap;
+ nsCString checksum;
+
+ CreateRandomSortedPrefixArray(1000, 4, 32, fArray);
+ PrefixArrayToPrefixStringMap(fArray, fMap);
+ CalculateCheckSum(fArray, checksum);
+
+ testFullUpdate(fMap, &checksum);
+
+ // Open lookup cache will load prefix set and verify the checksum
+ testOpenLookupCache();
+ }
+
+ Clear();
+
+ // Apply update without checksum
+ {
+ _PrefixArray fArray;
+ PrefixStringMap fMap;
+
+ CreateRandomSortedPrefixArray(1000, 4, 32, fArray);
+ PrefixArrayToPrefixStringMap(fArray, fMap);
+
+ testFullUpdate(fMap, nullptr);
+
+ testOpenLookupCache();
+ }
+
+ Clear();
+}
+
+// This test is used to avoid an eror from nsICryptoHash
+TEST(UrlClassifierTableUpdateV4, ApplyUpdateWithFixedChecksum)
+{
+ _PrefixArray fArray = { _Prefix("enus"), _Prefix("apollo"), _Prefix("mars"),
+ _Prefix("Hecatonchires cyclopes"),
+ _Prefix("vesta"), _Prefix("neptunus"), _Prefix("jupiter"),
+ _Prefix("diana"), _Prefix("minerva"), _Prefix("ceres"),
+ _Prefix("Aidos,Adephagia,Adikia,Aletheia"),
+ _Prefix("hecatonchires"), _Prefix("alcyoneus"), _Prefix("hades"),
+ _Prefix("vulcanus"), _Prefix("juno"), _Prefix("mercury"),
+ _Prefix("Stheno, Euryale and Medusa")
+ };
+ fArray.Sort();
+
+ PrefixStringMap fMap;
+ PrefixArrayToPrefixStringMap(fArray, fMap);
+
+ nsCString checksum("\xae\x18\x94\xd7\xd0\x83\x5f\xc1"
+ "\x58\x59\x5c\x2c\x72\xb9\x6e\x5e"
+ "\xf4\xe8\x0a\x6b\xff\x5e\x6b\x81"
+ "\x65\x34\x06\x16\x06\x59\xa0\x67");
+
+ testFullUpdate(fMap, &checksum);
+
+ // Open lookup cache will load prefix set and verify the checksum
+ testOpenLookupCache();
+
+ Clear();
+}
+
diff --git a/toolkit/components/url-classifier/tests/unit/test_listmanager.js b/toolkit/components/url-classifier/tests/unit/test_listmanager.js
index 0d297cb9f15c..ba11d930ee6f 100644
--- a/toolkit/components/url-classifier/tests/unit/test_listmanager.js
+++ b/toolkit/components/url-classifier/tests/unit/test_listmanager.js
@@ -69,7 +69,7 @@ let gUpdatedCntForTableData = 0; // For TEST_TABLE_DATA_LIST.
let gIsV4Updated = false; // For TEST_TABLE_DATA_V4.
const NEW_CLIENT_STATE = 'sta\0te';
-const CHECKSUM = 'check\0sum';
+const CHECKSUM = '\x30\x67\xc7\x2c\x5e\x50\x1c\x31\xe3\xfe\xca\x73\xf0\x47\xdc\x34\x1a\x95\x63\x99\xec\x70\x5e\x0a\xee\x9e\xfb\x17\xa1\x55\x35\x78';
prefBranch.setBoolPref("browser.safebrowsing.debug", true);
@@ -262,14 +262,14 @@ function run_test() {
// 'threat_type': 2, // SOCIAL_ENGINEERING_PUBLIC
// 'response_type': 2, // FULL_UPDATE
// 'new_client_state': 'sta\x00te', // NEW_CLIENT_STATE
- // 'checksum': { "sha256": 'check\x00sum' }, // CHECKSUM
+ // 'checksum': { "sha256": CHECKSUM }, // CHECKSUM
// 'additions': { 'compression_type': RAW,
// 'prefix_size': 4,
// 'raw_hashes': "00000001000000020000000300000004"}
// }
// ]
//
- let content = "\x0A\x33\x08\x02\x20\x02\x2A\x18\x08\x01\x12\x14\x08\x04\x12\x10\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x3A\x06\x73\x74\x61\x00\x74\x65\x42\x0B\x0A\x09\x63\x68\x65\x63\x6B\x00\x73\x75\x6D\x12\x08\x08\x08\x10\x80\x94\xEB\xDC\x03";
+ let content = "\x0A\x4A\x08\x02\x20\x02\x2A\x18\x08\x01\x12\x14\x08\x04\x12\x10\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x3A\x06\x73\x74\x61\x00\x74\x65\x42\x22\x0A\x20\x30\x67\xC7\x2C\x5E\x50\x1C\x31\xE3\xFE\xCA\x73\xF0\x47\xDC\x34\x1A\x95\x63\x99\xEC\x70\x5E\x0A\xEE\x9E\xFB\x17\xA1\x55\x35\x78\x12\x08\x08\x08\x10\x80\x94\xEB\xDC\x03";
response.bodyOutputStream.write(content, content.length);
diff --git a/toolkit/content/tests/widgets/mochitest.ini b/toolkit/content/tests/widgets/mochitest.ini
index f545ac0bf8bb..0385d100b479 100644
--- a/toolkit/content/tests/widgets/mochitest.ini
+++ b/toolkit/content/tests/widgets/mochitest.ini
@@ -29,6 +29,7 @@ tags = fullscreen
skip-if = toolkit == 'android' #TIMED_OUT
[test_videocontrols_vtt.html]
skip-if = toolkit == 'android'
+[test_videocontrols_iframe_fullscreen.html]
[test_videocontrols_audio.html]
[test_videocontrols_audio_direction.html]
[test_videocontrols_jsdisabled.html]
diff --git a/toolkit/content/tests/widgets/test_videocontrols_iframe_fullscreen.html b/toolkit/content/tests/widgets/test_videocontrols_iframe_fullscreen.html
new file mode 100644
index 000000000000..6391dcc1b74f
--- /dev/null
+++ b/toolkit/content/tests/widgets/test_videocontrols_iframe_fullscreen.html
@@ -0,0 +1,64 @@
+
+
+
+ Video controls test - iframe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/toolkit/content/widgets/browser.xml b/toolkit/content/widgets/browser.xml
index ae6ca4fdf538..f89191a088a3 100644
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -1202,6 +1202,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1275,8 +1316,7 @@
PopupNotifications._swapBrowserNotifications(aOtherBrowser, this);
try {
- this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner)
- .swapFrameLoaders(aOtherBrowser);
+ this.swapFrameLoaders(aOtherBrowser);
} catch (ex) {
// This may not be implemented for browser elements that are not
// attached to a BrowserDOMWindow.
diff --git a/widget/android/fennec/FennecJNIWrappers.cpp b/widget/android/fennec/FennecJNIWrappers.cpp
index 73b5ea876e32..f1ae22e6e597 100644
--- a/widget/android/fennec/FennecJNIWrappers.cpp
+++ b/widget/android/fennec/FennecJNIWrappers.cpp
@@ -248,6 +248,45 @@ constexpr char CodecProxy::NativeCallbacks::OnOutput_t::signature[];
constexpr char CodecProxy::NativeCallbacks::OnOutputFormatChanged_t::name[];
constexpr char CodecProxy::NativeCallbacks::OnOutputFormatChanged_t::signature[];
+const char MediaDrmProxy::name[] =
+ "org/mozilla/gecko/media/MediaDrmProxy";
+
+constexpr char MediaDrmProxy::CanDecode_t::name[];
+constexpr char MediaDrmProxy::CanDecode_t::signature[];
+
+auto MediaDrmProxy::CanDecode(mozilla::jni::String::Param a0) -> bool
+{
+ return mozilla::jni::Method::Call(MediaDrmProxy::Context(), nullptr, a0);
+}
+
+constexpr char MediaDrmProxy::IsCryptoSchemeSupported_t::name[];
+constexpr char MediaDrmProxy::IsCryptoSchemeSupported_t::signature[];
+
+auto MediaDrmProxy::IsCryptoSchemeSupported(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1) -> bool
+{
+ return mozilla::jni::Method::Call(MediaDrmProxy::Context(), nullptr, a0, a1);
+}
+
+constexpr char MediaDrmProxy::IsSchemeSupported_t::name[];
+constexpr char MediaDrmProxy::IsSchemeSupported_t::signature[];
+
+auto MediaDrmProxy::IsSchemeSupported(mozilla::jni::String::Param a0) -> bool
+{
+ return mozilla::jni::Method::Call(MediaDrmProxy::Context(), nullptr, a0);
+}
+
+const char16_t MediaDrmProxy::AAC[] = u"audio/mp4a-latm";
+
+const char16_t MediaDrmProxy::AVC[] = u"video/avc";
+
+const char16_t MediaDrmProxy::OPUS[] = u"audio/opus";
+
+const char16_t MediaDrmProxy::VORBIS[] = u"audio/vorbis";
+
+const char16_t MediaDrmProxy::VP8[] = u"video/x-vnd.on2.vp8";
+
+const char16_t MediaDrmProxy::VP9[] = u"video/x-vnd.on2.vp9";
+
const char Sample::name[] =
"org/mozilla/gecko/media/Sample";
diff --git a/widget/android/fennec/FennecJNIWrappers.h b/widget/android/fennec/FennecJNIWrappers.h
index d573cc9df46e..a57a4cbd98d6 100644
--- a/widget/android/fennec/FennecJNIWrappers.h
+++ b/widget/android/fennec/FennecJNIWrappers.h
@@ -922,6 +922,91 @@ public:
template class Natives;
};
+class MediaDrmProxy : public mozilla::jni::ObjectBase
+{
+public:
+ static const char name[];
+
+ explicit MediaDrmProxy(const Context& ctx) : ObjectBase(ctx) {}
+
+ struct CanDecode_t {
+ typedef MediaDrmProxy Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "CanDecode";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto CanDecode(mozilla::jni::String::Param) -> bool;
+
+ struct IsCryptoSchemeSupported_t {
+ typedef MediaDrmProxy Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "IsCryptoSchemeSupported";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;)Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto IsCryptoSchemeSupported(mozilla::jni::String::Param, mozilla::jni::String::Param) -> bool;
+
+ struct IsSchemeSupported_t {
+ typedef MediaDrmProxy Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "isSchemeSupported";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto IsSchemeSupported(mozilla::jni::String::Param) -> bool;
+
+ static const char16_t AAC[];
+
+ static const char16_t AVC[];
+
+ static const char16_t OPUS[];
+
+ static const char16_t VORBIS[];
+
+ static const char16_t VP8[];
+
+ static const char16_t VP9[];
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
class Sample : public mozilla::jni::ObjectBase
{
public:
diff --git a/xpcom/threads/MozPromise.h b/xpcom/threads/MozPromise.h
index b2c17e322f50..3de858a15779 100644
--- a/xpcom/threads/MozPromise.h
+++ b/xpcom/threads/MozPromise.h
@@ -14,6 +14,7 @@
#include "mozilla/Mutex.h"
#include "mozilla/Monitor.h"
#include "mozilla/Tuple.h"
+#include "mozilla/TypeTraits.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"
@@ -968,6 +969,23 @@ private:
nsAutoPtr> mMethodCall;
};
+constexpr bool Any()
+{
+ return false;
+}
+
+template
+constexpr bool Any(T1 a)
+{
+ return static_cast(a);
+}
+
+template
+constexpr bool Any(T1 a, Ts... aOthers)
+{
+ return a || Any(aOthers...);
+}
+
} // namespace detail
template
@@ -975,6 +993,8 @@ static RefPtr
InvokeAsync(AbstractThread* aTarget, ThisType* aThisVal, const char* aCallerName,
RefPtr(ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs)
{
+ static_assert(!detail::Any(IsReference::value...),
+ "Cannot pass reference types through InvokeAsync, see bug 1313497 if you require it");
typedef detail::MethodCall MethodCallType;
typedef detail::ProxyRunnable ProxyRunnableType;