mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central to b2g-inbound
This commit is contained in:
commit
a5bb018cf4
@ -15,8 +15,8 @@
|
||||
"unpack": true
|
||||
},
|
||||
{
|
||||
"size": 73029932,
|
||||
"digest": "ef1818acf065838dcb72554e521f9fd7098f0a3690cb6a3106d7bf18f46c342bfdd5a2b7d86e92ee3ddb9e478380343e58ecf8fd242807b8881a2d53fbec5ab3",
|
||||
"size": 193213220,
|
||||
"digest": "58b8ebd8de923117831dcbba71172a53e26c25bd16c2b2bb363a1254f2cd4e87f95e2c5f41e2dce10e18e43a17e0a395637c9ddcbf1e27673582792f9095c62e",
|
||||
"algorithm": "sha512",
|
||||
"filename": "rustc.tar.xz",
|
||||
"unpack": true
|
||||
|
@ -5197,8 +5197,10 @@ var TabletModeUpdater = {
|
||||
};
|
||||
|
||||
var gTabletModePageCounter = {
|
||||
enabled: false,
|
||||
inc() {
|
||||
if (!AppConstants.isPlatformAndVersionAtLeast("win", "10.0")) {
|
||||
this.enabled = AppConstants.isPlatformAndVersionAtLeast("win", "10.0");
|
||||
if (!this.enabled) {
|
||||
this.inc = () => {};
|
||||
return;
|
||||
}
|
||||
@ -5214,8 +5216,11 @@ var gTabletModePageCounter = {
|
||||
},
|
||||
|
||||
finish() {
|
||||
Services.telemetry.getKeyedHistogramById("FX_TABLETMODE_PAGE_LOAD").add("tablet", this._tabletCount);
|
||||
Services.telemetry.getKeyedHistogramById("FX_TABLETMODE_PAGE_LOAD").add("desktop", this._desktopCount);
|
||||
if (this.enabled) {
|
||||
let histogram = Services.telemetry.getKeyedHistogramById("FX_TABLETMODE_PAGE_LOAD");
|
||||
histogram.add("tablet", this._tabletCount);
|
||||
histogram.add("desktop", this._desktopCount);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -4691,6 +4691,9 @@
|
||||
let hoveredTab = this._hoveredTab;
|
||||
if (hoveredTab) {
|
||||
hoveredTab._mouseleave();
|
||||
}
|
||||
hoveredTab = this.querySelector("tab:hover");
|
||||
if (hoveredTab) {
|
||||
hoveredTab._mouseenter();
|
||||
}
|
||||
]]></body>
|
||||
|
@ -14,17 +14,17 @@
|
||||
"unpack": true
|
||||
},
|
||||
{
|
||||
"size": 193213220,
|
||||
"digest": "58b8ebd8de923117831dcbba71172a53e26c25bd16c2b2bb363a1254f2cd4e87f95e2c5f41e2dce10e18e43a17e0a395637c9ddcbf1e27673582792f9095c62e",
|
||||
"algorithm": "sha512",
|
||||
"filename": "rustc.tar.xz",
|
||||
"unpack": true
|
||||
},
|
||||
{
|
||||
"size": 167175,
|
||||
"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
|
||||
"algorithm": "sha512",
|
||||
"filename": "sccache.tar.bz2",
|
||||
"unpack": true
|
||||
},
|
||||
{
|
||||
"size": 73029932,
|
||||
"digest": "ef1818acf065838dcb72554e521f9fd7098f0a3690cb6a3106d7bf18f46c342bfdd5a2b7d86e92ee3ddb9e478380343e58ecf8fd242807b8881a2d53fbec5ab3",
|
||||
"algorithm": "sha512",
|
||||
"filename": "rustc.tar.xz",
|
||||
"unpack": true
|
||||
}
|
||||
]
|
||||
|
@ -10,8 +10,8 @@
|
||||
"unpack": true
|
||||
},
|
||||
{
|
||||
"size": 128301120,
|
||||
"digest": "d2d71103a6cec84b150b8f08bfef2682aa713c2d6eadce584f79836ef27ba4380ffb444165d999b79605f18ad165641a7a8cc0e04a201675ad5f655a59adbda9",
|
||||
"size": 215952362,
|
||||
"digest": "5e9825dbe83b2a157879076da70fc5c989a1638e30d3b14a9901b166db09013c356a9dc4eaf6c16209a1832d9cb1c67ca869e9b9003cab8355a7f03b3dc08775",
|
||||
"algorithm": "sha512",
|
||||
"filename": "rustc.tar.bz2",
|
||||
"unpack": true
|
||||
|
@ -30,6 +30,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
|
||||
"resource://gre/modules/TelemetryEnvironment.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryLog",
|
||||
"resource://gre/modules/TelemetryLog.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryUtils",
|
||||
"resource://gre/modules/TelemetryUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
|
||||
"resource://services-common/utils.js");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Metrics",
|
||||
@ -160,10 +162,6 @@ function loadJSONAsync(file, options) {
|
||||
});
|
||||
}
|
||||
|
||||
function telemetryEnabled() {
|
||||
return gPrefsTelemetry.get(PREF_TELEMETRY_ENABLED, false);
|
||||
}
|
||||
|
||||
// Returns a promise that is resolved with the AddonInstall for that URL.
|
||||
function addonInstallForURL(url, hash) {
|
||||
let deferred = Promise.defer();
|
||||
@ -389,7 +387,7 @@ Experiments.Experiments.prototype = {
|
||||
this._shutdown = false;
|
||||
configureLogging();
|
||||
|
||||
gExperimentsEnabled = gPrefs.get(PREF_ENABLED, false);
|
||||
gExperimentsEnabled = gPrefs.get(PREF_ENABLED, false) && TelemetryUtils.isTelemetryEnabled;
|
||||
this._log.trace("enabled=" + gExperimentsEnabled + ", " + this.enabled);
|
||||
|
||||
gPrefs.observe(PREF_LOGGING, configureLogging);
|
||||
@ -580,7 +578,7 @@ Experiments.Experiments.prototype = {
|
||||
_toggleExperimentsEnabled: Task.async(function* (enabled) {
|
||||
this._log.trace("_toggleExperimentsEnabled(" + enabled + ")");
|
||||
let wasEnabled = gExperimentsEnabled;
|
||||
gExperimentsEnabled = enabled && telemetryEnabled();
|
||||
gExperimentsEnabled = enabled && TelemetryUtils.isTelemetryEnabled;
|
||||
|
||||
if (wasEnabled == gExperimentsEnabled) {
|
||||
return;
|
||||
|
@ -233,3 +233,7 @@ function replaceExperiments(experiment, list) {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Experiments require Telemetry to be enabled, and that's not true for debug
|
||||
// builds. Let's just enable it here instead of going through each test.
|
||||
Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true);
|
||||
|
21
browser/experiments/test/xpcshell/test_telemetry_disabled.js
Normal file
21
browser/experiments/test/xpcshell/test_telemetry_disabled.js
Normal file
@ -0,0 +1,21 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
|
||||
add_test(function test_experiments_activation() {
|
||||
do_get_profile();
|
||||
loadAddonManager();
|
||||
|
||||
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
|
||||
Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, false);
|
||||
|
||||
let experiments = Experiments.instance();
|
||||
Assert.ok(!experiments.enabled, "Experiments must be disabled if Telemetry is disabled.");
|
||||
|
||||
// TODO: Test that Experiments are turned back on when bug 1232648 lands.
|
||||
|
||||
run_next_test();
|
||||
});
|
@ -24,6 +24,7 @@ generated-files =
|
||||
[test_disableExperiments.js]
|
||||
[test_fetch.js]
|
||||
[test_telemetry.js]
|
||||
[test_telemetry_disabled.js]
|
||||
[test_healthreport.js]
|
||||
[test_previous_provider.js]
|
||||
[test_upgrade.js]
|
||||
|
@ -72,7 +72,7 @@ SEARCHPLUGINS_NAMES = $(shell cat $(call MERGE_FILE,/searchplugins/list.txt)) dd
|
||||
SEARCHPLUGINS_FILENAMES = $(subst :hidden,,$(SEARCHPLUGINS_NAMES))
|
||||
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))),$(info Missing searchplugin: $(plugin))))
|
||||
SEARCHPLUGINS := $(foreach plugin,$(addsuffix .xml,$(SEARCHPLUGINS_FILENAMES)),$(or $(wildcard $(call EN_US_OR_L10N_FILE,searchplugins/$(plugin))),$(warning Missing searchplugin: $(plugin))))
|
||||
# Some locale-specific search plugins may have preprocessor directives, but the
|
||||
# default en-US ones do not.
|
||||
SEARCHPLUGINS_FLAGS := --silence-missing-directive-warnings
|
||||
|
@ -123,7 +123,7 @@ groupbox.collapsable caption .caption-icon {
|
||||
background-position: center;
|
||||
-moz-margin-start: 2px;
|
||||
-moz-margin-end: 2px;
|
||||
background-image: url("chrome://global/skin/tree/twisty-open.png");
|
||||
background-image: url("chrome://global/skin/tree/twisty.svg#open");
|
||||
}
|
||||
|
||||
groupbox.collapsable[closed="true"] {
|
||||
@ -133,7 +133,7 @@ groupbox.collapsable[closed="true"] {
|
||||
}
|
||||
|
||||
groupbox.collapsable[closed="true"] caption .caption-icon {
|
||||
background-image: url("chrome://global/skin/tree/twisty-clsd.png");
|
||||
background-image: url("chrome://global/skin/tree/twisty.svg#clsd");
|
||||
}
|
||||
|
||||
groupbox tree {
|
||||
|
@ -25,10 +25,10 @@ AC_DEFUN([MOZ_RUST_SUPPORT], [
|
||||
fi
|
||||
if test -n "$MOZ_RUST" && test -z "$_RUSTC_MAJOR_VERSION" -o \
|
||||
"$_RUSTC_MAJOR_VERSION" -lt 1 -o \
|
||||
\( "$_RUSTC_MAJOR_VERSION" -eq 1 -a "$_RUSTC_MINOR_VERSION" -lt 4 \); then
|
||||
\( "$_RUSTC_MAJOR_VERSION" -eq 1 -a "$_RUSTC_MINOR_VERSION" -lt 5 \); then
|
||||
AC_MSG_ERROR([Rust compiler ${RUSTC_VERSION} is too old.
|
||||
To compile Rust language sources please install at least
|
||||
version 1.4 of the 'rustc' toolchain and make sure it is
|
||||
version 1.5 of the 'rustc' toolchain and make sure it is
|
||||
first in your path.
|
||||
You can verify this by typing 'rustc --version'.])
|
||||
fi
|
||||
|
@ -945,7 +945,7 @@ ifdef MOZ_RUST
|
||||
# in the target's LIBS.
|
||||
$(RSOBJS):
|
||||
$(REPORT_BUILD)
|
||||
$(RUSTC) $(RUSTFLAGS) --crate-type staticlib -o $(call mk_libname,$<) $(_VPATH_SRCS)
|
||||
$(RUSTC) $(RUSTFLAGS) --crate-type staticlib --emit dep-info=$(MDDEPDIR)/$(call mk_libname,$<).pp,link=$(call mk_libname,$<) $(_VPATH_SRCS)
|
||||
endif
|
||||
|
||||
$(SOBJS):
|
||||
|
@ -6405,14 +6405,6 @@ AC_SUBST(TAR)
|
||||
AC_CHECK_PROGS(WGET, wget, "")
|
||||
AC_SUBST(WGET)
|
||||
|
||||
dnl ========================================================
|
||||
dnl Signing
|
||||
dnl ========================================================
|
||||
|
||||
if test -n "$MOZ_SIGN_CMD"; then
|
||||
AC_DEFINE(MOZ_SIGNING)
|
||||
fi
|
||||
|
||||
dnl ========================================================
|
||||
dnl Maintenance Service
|
||||
dnl ========================================================
|
||||
|
@ -2,27 +2,20 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test for dynamically registering and unregistering themes
|
||||
const CHROME_URL = "chrome://mochitests/content/browser/devtools/client/framework/test/";
|
||||
|
||||
var toolbox;
|
||||
|
||||
function test()
|
||||
{
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
add_task(function* themeRegistration() {
|
||||
let tab = yield addTab("data:text/html,test");
|
||||
let target = TargetFactory.forTab(tab);
|
||||
toolbox = yield gDevTools.showToolbox(target);
|
||||
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
|
||||
gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
|
||||
gDevTools.showToolbox(target).then(testRegister);
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html,test for dynamically registering and unregistering themes";
|
||||
}
|
||||
|
||||
function testRegister(aToolbox)
|
||||
{
|
||||
toolbox = aToolbox
|
||||
gDevTools.once("theme-registered", themeRegistered);
|
||||
let themeId = yield new Promise(resolve => {
|
||||
gDevTools.once("theme-registered", (e, themeId) => {
|
||||
resolve(themeId);
|
||||
});
|
||||
|
||||
gDevTools.registerTheme({
|
||||
id: "test-theme",
|
||||
@ -30,32 +23,24 @@ function testRegister(aToolbox)
|
||||
stylesheets: [CHROME_URL + "doc_theme.css"],
|
||||
classList: ["theme-test"],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function themeRegistered(event, themeId)
|
||||
{
|
||||
is(themeId, "test-theme", "theme-registered event handler sent theme id");
|
||||
|
||||
ok(gDevTools.getThemeDefinitionMap().has(themeId), "theme added to map");
|
||||
});
|
||||
|
||||
add_task(function* themeInOptionsPanel() {
|
||||
|
||||
yield toolbox.selectTool("options");
|
||||
|
||||
// Test that new theme appears in the Options panel
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
gDevTools.showToolbox(target, "options").then(() => {
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
let doc = panel.panelWin.frameElement.contentDocument;
|
||||
let panelWin = toolbox.getCurrentPanel().panelWin;
|
||||
let doc = panelWin.frameElement.contentDocument;
|
||||
let themeOption = doc.querySelector("#devtools-theme-box > radio[value=test-theme]");
|
||||
|
||||
ok(themeOption, "new theme exists in the Options panel");
|
||||
|
||||
// Apply the new theme.
|
||||
applyTheme();
|
||||
});
|
||||
}
|
||||
|
||||
function applyTheme()
|
||||
{
|
||||
let panelWin = toolbox.getCurrentPanel().panelWin;
|
||||
let doc = panelWin.frameElement.contentDocument;
|
||||
let testThemeOption = doc.querySelector("#devtools-theme-box > radio[value=test-theme]");
|
||||
let lightThemeOption = doc.querySelector("#devtools-theme-box > radio[value=light]");
|
||||
|
||||
@ -65,24 +50,26 @@ function applyTheme()
|
||||
// Select test theme.
|
||||
testThemeOption.click();
|
||||
|
||||
info("Waiting for theme to finish loading");
|
||||
yield once(panelWin, "theme-switch-complete");
|
||||
|
||||
color = panelWin.getComputedStyle(testThemeOption).color;
|
||||
is(color, "rgb(255, 0, 0)", "style applied");
|
||||
|
||||
// Select light theme
|
||||
lightThemeOption.click();
|
||||
|
||||
info("Waiting for theme to finish loading");
|
||||
yield once(panelWin, "theme-switch-complete");
|
||||
|
||||
color = panelWin.getComputedStyle(testThemeOption).color;
|
||||
isnot(color, "rgb(255, 0, 0)", "style unapplied");
|
||||
|
||||
// Select test theme again.
|
||||
testThemeOption.click();
|
||||
});
|
||||
|
||||
// Then unregister the test theme.
|
||||
testUnregister();
|
||||
}
|
||||
|
||||
function testUnregister()
|
||||
{
|
||||
add_task(function* themeUnregistration() {
|
||||
gDevTools.unregisterTheme("test-theme");
|
||||
|
||||
ok(!gDevTools.getThemeDefinitionMap().has("test-theme"), "theme removed from map");
|
||||
@ -94,20 +81,9 @@ function testUnregister()
|
||||
// The default light theme must be selected now.
|
||||
is(themeBox.selectedItem, themeBox.querySelector("[value=light]"),
|
||||
"theme light must be selected");
|
||||
|
||||
// Make sure the tab-attaching process is done before we destroy the toolbox.
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let actor = target.activeTab.actor;
|
||||
target.client.attachTab(actor, (response) => {
|
||||
cleanup();
|
||||
});
|
||||
}
|
||||
|
||||
function cleanup()
|
||||
{
|
||||
toolbox.destroy().then(function() {
|
||||
add_task(function* cleanup() {
|
||||
yield toolbox.destroy();
|
||||
toolbox = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
@ -39,24 +39,24 @@ devtools.jar:
|
||||
content/animationinspector/animation-controller.js (animationinspector/animation-controller.js)
|
||||
content/animationinspector/animation-panel.js (animationinspector/animation-panel.js)
|
||||
content/animationinspector/animation-inspector.xhtml (animationinspector/animation-inspector.xhtml)
|
||||
content/sourceeditor/codemirror/comment/comment.js (sourceeditor/codemirror/addon/comment/comment.js)
|
||||
content/sourceeditor/codemirror/edit/trailingspace.js (sourceeditor/codemirror/addon/edit/trailingspace.js)
|
||||
content/sourceeditor/codemirror/edit/matchbrackets.js (sourceeditor/codemirror/addon/edit/matchbrackets.js)
|
||||
content/sourceeditor/codemirror/edit/closebrackets.js (sourceeditor/codemirror/addon/edit/closebrackets.js)
|
||||
content/sourceeditor/codemirror/dialog/dialog.js (sourceeditor/codemirror/addon/dialog/dialog.js)
|
||||
content/sourceeditor/codemirror/dialog/dialog.css (sourceeditor/codemirror/addon/dialog/dialog.css)
|
||||
content/sourceeditor/codemirror/fold/foldcode.js (sourceeditor/codemirror/addon/fold/foldcode.js)
|
||||
content/sourceeditor/codemirror/fold/brace-fold.js (sourceeditor/codemirror/addon/fold/brace-fold.js)
|
||||
content/sourceeditor/codemirror/fold/comment-fold.js (sourceeditor/codemirror/addon/fold/comment-fold.js)
|
||||
content/sourceeditor/codemirror/fold/xml-fold.js (sourceeditor/codemirror/addon/fold/xml-fold.js)
|
||||
content/sourceeditor/codemirror/fold/foldgutter.js (sourceeditor/codemirror/addon/fold/foldgutter.js)
|
||||
content/sourceeditor/codemirror/hint/show-hint.js (sourceeditor/codemirror/addon/hint/show-hint.js)
|
||||
content/sourceeditor/codemirror/search/search.js (sourceeditor/codemirror/addon/search/search.js)
|
||||
content/sourceeditor/codemirror/search/searchcursor.js (sourceeditor/codemirror/addon/search/searchcursor.js)
|
||||
content/sourceeditor/codemirror/selection/active-line.js (sourceeditor/codemirror/addon/selection/active-line.js)
|
||||
content/sourceeditor/codemirror/tern/tern.js (sourceeditor/codemirror/addon/tern/tern.js)
|
||||
content/sourceeditor/codemirror/codemirror.js (sourceeditor/codemirror/lib/codemirror.js)
|
||||
content/sourceeditor/codemirror/codemirror.css (sourceeditor/codemirror/lib/codemirror.css)
|
||||
content/sourceeditor/codemirror/addon/comment/comment.js (sourceeditor/codemirror/addon/comment/comment.js)
|
||||
content/sourceeditor/codemirror/addon/edit/trailingspace.js (sourceeditor/codemirror/addon/edit/trailingspace.js)
|
||||
content/sourceeditor/codemirror/addon/edit/matchbrackets.js (sourceeditor/codemirror/addon/edit/matchbrackets.js)
|
||||
content/sourceeditor/codemirror/addon/edit/closebrackets.js (sourceeditor/codemirror/addon/edit/closebrackets.js)
|
||||
content/sourceeditor/codemirror/addon/dialog/dialog.js (sourceeditor/codemirror/addon/dialog/dialog.js)
|
||||
content/sourceeditor/codemirror/addon/dialog/dialog.css (sourceeditor/codemirror/addon/dialog/dialog.css)
|
||||
content/sourceeditor/codemirror/addon/fold/foldcode.js (sourceeditor/codemirror/addon/fold/foldcode.js)
|
||||
content/sourceeditor/codemirror/addon/fold/brace-fold.js (sourceeditor/codemirror/addon/fold/brace-fold.js)
|
||||
content/sourceeditor/codemirror/addon/fold/comment-fold.js (sourceeditor/codemirror/addon/fold/comment-fold.js)
|
||||
content/sourceeditor/codemirror/addon/fold/xml-fold.js (sourceeditor/codemirror/addon/fold/xml-fold.js)
|
||||
content/sourceeditor/codemirror/addon/fold/foldgutter.js (sourceeditor/codemirror/addon/fold/foldgutter.js)
|
||||
content/sourceeditor/codemirror/addon/hint/show-hint.js (sourceeditor/codemirror/addon/hint/show-hint.js)
|
||||
content/sourceeditor/codemirror/addon/search/search.js (sourceeditor/codemirror/addon/search/search.js)
|
||||
content/sourceeditor/codemirror/addon/search/searchcursor.js (sourceeditor/codemirror/addon/search/searchcursor.js)
|
||||
content/sourceeditor/codemirror/addon/selection/active-line.js (sourceeditor/codemirror/addon/selection/active-line.js)
|
||||
content/sourceeditor/codemirror/addon/tern/tern.js (sourceeditor/codemirror/addon/tern/tern.js)
|
||||
content/sourceeditor/codemirror/lib/codemirror.js (sourceeditor/codemirror/lib/codemirror.js)
|
||||
content/sourceeditor/codemirror/lib/codemirror.css (sourceeditor/codemirror/lib/codemirror.css)
|
||||
content/sourceeditor/codemirror/mode/javascript.js (sourceeditor/codemirror/mode/javascript.js)
|
||||
content/sourceeditor/codemirror/mode/xml.js (sourceeditor/codemirror/mode/xml.js)
|
||||
content/sourceeditor/codemirror/mode/css.js (sourceeditor/codemirror/mode/css.js)
|
||||
|
@ -7,7 +7,8 @@ var toolbox;
|
||||
add_task(function*() {
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let toolbox = yield gDevTools.showToolbox(target);
|
||||
let root = toolbox.frame.contentDocument.documentElement;
|
||||
let doc = toolbox.frame.contentDocument;
|
||||
let root = doc.documentElement;
|
||||
|
||||
let platform = root.getAttribute("platform");
|
||||
let expectedPlatform = getPlatform();
|
||||
@ -15,7 +16,24 @@ add_task(function*() {
|
||||
|
||||
let theme = Services.prefs.getCharPref("devtools.theme");
|
||||
let className = "theme-" + theme;
|
||||
ok(root.classList.contains(className), ":root has " + className + " class (current theme)");
|
||||
ok(root.classList.contains(className),
|
||||
":root has " + className + " class (current theme)");
|
||||
|
||||
// Convert the xpath result into an array of strings
|
||||
// like `href="{URL}" type="text/css"`
|
||||
let sheetsIterator = doc.evaluate("processing-instruction('xml-stylesheet')",
|
||||
doc, null, XPathResult.ANY_TYPE, null);
|
||||
let sheetsInDOM = [];
|
||||
let sheet;
|
||||
while (sheet = sheetsIterator.iterateNext()) {
|
||||
sheetsInDOM.push(sheet.data);
|
||||
}
|
||||
|
||||
let sheetsFromTheme = gDevTools.getThemeDefinition(theme).stylesheets;
|
||||
info ("Checking for existence of " + sheetsInDOM.length + " sheets");
|
||||
for (let sheet of sheetsFromTheme) {
|
||||
ok(sheetsInDOM.some(s=>s.includes(sheet)), "There is a stylesheet for " + sheet);
|
||||
}
|
||||
|
||||
yield toolbox.destroy();
|
||||
});
|
||||
|
@ -3,8 +3,9 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
(function() {
|
||||
const DEVTOOLS_SKIN_URL = "chrome://devtools/skin/";
|
||||
const SCROLLBARS_URL = "chrome://devtools/skin/floating-scrollbars-light.css";
|
||||
let documentElement = document.documentElement;
|
||||
let devtoolsStyleSheets = new WeakMap();
|
||||
|
||||
function forceStyle() {
|
||||
let computedStyle = window.getComputedStyle(documentElement);
|
||||
@ -19,6 +20,45 @@
|
||||
documentElement.style.display = display; // Restore
|
||||
}
|
||||
|
||||
/*
|
||||
* Append a new processing instruction and return an object with
|
||||
* - styleSheet: DOMNode
|
||||
* - loadPromise: Promise that resolves once the sheets loads or errors
|
||||
*/
|
||||
function appendStyleSheet(url) {
|
||||
let styleSheetAttr = `href="${url}" type="text/css"`;
|
||||
let styleSheet = document.createProcessingInstruction(
|
||||
"xml-stylesheet", styleSheetAttr);
|
||||
let loadPromise = new Promise((resolve, reject) => {
|
||||
function onload() {
|
||||
styleSheet.removeEventListener("load", onload);
|
||||
styleSheet.removeEventListener("error", onerror);
|
||||
resolve();
|
||||
}
|
||||
function onerror() {
|
||||
styleSheet.removeEventListener("load", onload);
|
||||
styleSheet.removeEventListener("error", onerror);
|
||||
reject("Failed to load theme file " + url);
|
||||
}
|
||||
|
||||
styleSheet.addEventListener("load", onload);
|
||||
styleSheet.addEventListener("error", onerror);
|
||||
});
|
||||
document.insertBefore(styleSheet, documentElement);
|
||||
return {styleSheet, loadPromise};
|
||||
}
|
||||
|
||||
/*
|
||||
* Notify the window that a theme switch finished so tests can check the DOM
|
||||
*/
|
||||
function notifyWindow() {
|
||||
window.dispatchEvent(new CustomEvent("theme-switch-complete", {}));
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply all the sheets from `newTheme` and remove all of the sheets
|
||||
* from `oldTheme`
|
||||
*/
|
||||
function switchTheme(newTheme, oldTheme) {
|
||||
if (newTheme === oldTheme) {
|
||||
return;
|
||||
@ -28,8 +68,8 @@
|
||||
|
||||
// Unload all theme stylesheets related to the old theme.
|
||||
if (oldThemeDef) {
|
||||
for (let url of oldThemeDef.stylesheets) {
|
||||
StylesheetUtils.removeSheet(window, url, "author");
|
||||
for (let sheet of devtoolsStyleSheets.get(oldThemeDef) || []) {
|
||||
sheet.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,8 +82,17 @@
|
||||
newThemeDef = gDevTools.getThemeDefinition("light");
|
||||
}
|
||||
|
||||
// Store the sheets in a WeakMap for access later when the theme gets
|
||||
// unapplied. It's hard to query for processing instructions so this
|
||||
// is an easy way to access them later without storing a property on
|
||||
// the window
|
||||
devtoolsStyleSheets.set(newThemeDef, []);
|
||||
|
||||
let loadEvents = [];
|
||||
for (let url of newThemeDef.stylesheets) {
|
||||
StylesheetUtils.loadSheet(window, url, "author");
|
||||
let {styleSheet,loadPromise} = appendStyleSheet(url);
|
||||
devtoolsStyleSheets.get(newThemeDef).push(styleSheet);
|
||||
loadEvents.push(loadPromise);
|
||||
}
|
||||
|
||||
// Floating scroll-bars like in OSX
|
||||
@ -53,21 +102,10 @@
|
||||
|
||||
// TODO: extensions might want to customize scrollbar styles too.
|
||||
if (!hiddenDOMWindow.matchMedia("(-moz-overlay-scrollbars)").matches) {
|
||||
let scrollbarsUrl = Services.io.newURI(
|
||||
DEVTOOLS_SKIN_URL + "floating-scrollbars-light.css", null, null);
|
||||
|
||||
if (newTheme == "dark") {
|
||||
StylesheetUtils.loadSheet(
|
||||
window,
|
||||
scrollbarsUrl,
|
||||
"agent"
|
||||
);
|
||||
StylesheetUtils.loadSheet(window, SCROLLBARS_URL, "agent");
|
||||
} else if (oldTheme == "dark") {
|
||||
StylesheetUtils.removeSheet(
|
||||
window,
|
||||
scrollbarsUrl,
|
||||
"agent"
|
||||
);
|
||||
StylesheetUtils.removeSheet(window, SCROLLBARS_URL, "agent");
|
||||
}
|
||||
forceStyle();
|
||||
}
|
||||
@ -92,6 +130,8 @@
|
||||
|
||||
// Final notification for further theme-switching related logic.
|
||||
gDevTools.emit("theme-switched", window, newTheme, oldTheme);
|
||||
|
||||
Promise.all(loadEvents).then(notifyWindow, console.error.bind(console));
|
||||
}
|
||||
|
||||
function handlePrefChange(event, data) {
|
||||
@ -101,7 +141,6 @@
|
||||
}
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://devtools/client/framework/gDevTools.jsm");
|
||||
const {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
|
||||
|
@ -8,8 +8,8 @@ const cssAutoCompleter = require("devtools/client/sourceeditor/css-autocompleter
|
||||
const { AutocompletePopup } = require("devtools/client/shared/autocomplete-popup");
|
||||
|
||||
const CM_TERN_SCRIPTS = [
|
||||
"chrome://devtools/content/sourceeditor/codemirror/tern/tern.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/hint/show-hint.js"
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/tern/tern.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/hint/show-hint.js"
|
||||
];
|
||||
|
||||
const autocompleteMap = new WeakMap();
|
||||
|
@ -47,35 +47,35 @@ const { OS } = Services.appinfo;
|
||||
|
||||
const CM_STYLES = [
|
||||
"chrome://devtools/skin/common.css",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/codemirror.css",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/dialog/dialog.css",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/mozilla.css"
|
||||
];
|
||||
|
||||
const CM_SCRIPTS = [
|
||||
"chrome://devtools/content/shared/theme-switching.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/codemirror.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/dialog/dialog.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/search/searchcursor.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/search/search.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/edit/matchbrackets.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/edit/closebrackets.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/comment/comment.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/search/searchcursor.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/search/search.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/edit/matchbrackets.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/edit/closebrackets.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/comment/comment.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/mode/javascript.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/mode/xml.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/mode/css.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/mode/htmlmixed.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/mode/clike.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/selection/active-line.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/edit/trailingspace.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/selection/active-line.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/edit/trailingspace.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/keymap/emacs.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/keymap/vim.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/keymap/sublime.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/fold/foldcode.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/fold/brace-fold.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/fold/comment-fold.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/fold/xml-fold.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/fold/foldgutter.js"
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/fold/foldcode.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/fold/brace-fold.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/fold/comment-fold.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/fold/xml-fold.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/fold/foldgutter.js"
|
||||
];
|
||||
|
||||
const CM_IFRAME =
|
||||
|
@ -3,15 +3,15 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>CodeMirror: Basic Tests</title>
|
||||
<link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/codemirror.css">
|
||||
<link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css">
|
||||
<link rel="stylesheet" href="cm_mode_test.css">
|
||||
<!--<link rel="stylesheet" href="../doc/docs.css">-->
|
||||
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/codemirror.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/search/searchcursor.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/dialog/dialog.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/edit/matchbrackets.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/comment/comment.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/addon/search/searchcursor.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/addon/edit/matchbrackets.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/addon/comment/comment.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/mode/javascript.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/keymap/vim.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/keymap/emacs.js"></script>
|
||||
|
@ -3,15 +3,15 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>CodeMirror: VIM/Emacs tests</title>
|
||||
<link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/codemirror.css">
|
||||
<link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css">
|
||||
<link rel="stylesheet" href="cm_mode_test.css">
|
||||
<!--<link rel="stylesheet" href="../doc/docs.css">-->
|
||||
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/codemirror.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/search/searchcursor.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/dialog/dialog.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/edit/matchbrackets.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/comment/comment.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/addon/search/searchcursor.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/addon/edit/matchbrackets.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/addon/comment/comment.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/mode/javascript.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/keymap/vim.js"></script>
|
||||
<script src="chrome://devtools/content/sourceeditor/codemirror/keymap/sublime.js"></script>
|
||||
|
@ -290,7 +290,7 @@ body {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
--timelime-border-color: var(--theme-body-color);
|
||||
--timeline-border-color: var(--theme-body-color);
|
||||
--timeline-background-color: var(--theme-splitter-color);
|
||||
|
||||
/* Iterations of the animation are displayed with a repeating linear-gradient
|
||||
@ -299,25 +299,25 @@ body {
|
||||
the border of this element */
|
||||
background-image:
|
||||
linear-gradient(to right,
|
||||
var(--timelime-border-color) 0,
|
||||
var(--timelime-border-color) 1px,
|
||||
var(--timeline-border-color) 0,
|
||||
var(--timeline-border-color) 1px,
|
||||
transparent 1px,
|
||||
transparent 2px);
|
||||
background-repeat: repeat-x;
|
||||
background-position: -1px 0;
|
||||
border: 1px solid var(--timelime-border-color);
|
||||
border: 1px solid var(--timeline-border-color);
|
||||
|
||||
/* The background color is set independently */
|
||||
background-color: var(--timeline-background-color);
|
||||
}
|
||||
|
||||
.animation-timeline .animation .cssanimation {
|
||||
--timelime-border-color: var(--theme-highlight-lightorange);
|
||||
--timeline-border-color: var(--theme-highlight-lightorange);
|
||||
--timeline-background-color: var(--theme-contrast-background);
|
||||
}
|
||||
|
||||
.animation-timeline .animation .csstransition {
|
||||
--timelime-border-color: var(--theme-highlight-bluegrey);
|
||||
--timeline-border-color: var(--theme-highlight-bluegrey);
|
||||
--timeline-background-color: var(--theme-highlight-blue);
|
||||
}
|
||||
|
||||
@ -369,14 +369,14 @@ body {
|
||||
box-sizing: border-box;
|
||||
height: calc(100% + 2px);
|
||||
|
||||
border: 1px solid var(--timelime-border-color);
|
||||
border: 1px solid var(--timeline-border-color);
|
||||
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(--timelime-border-color);
|
||||
background-color: var(--timeline-border-color);
|
||||
}
|
||||
|
||||
.animation-timeline .animation .delay.negative {
|
||||
|
@ -6,7 +6,7 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.theme-sidebar body {
|
||||
body.theme-sidebar {
|
||||
/* The view will grow bigger as the window gets resized, until 400px */
|
||||
max-width: 400px;
|
||||
margin: 0px auto;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,25 +4,11 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/devtools/DominatorTree.h"
|
||||
#include "js/Debug.h"
|
||||
#include "mozilla/CycleCollectedJSRuntime.h"
|
||||
#include "mozilla/dom/DominatorTreeBinding.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace devtools {
|
||||
|
||||
static MallocSizeOf
|
||||
getCurrentThreadDebuggerMallocSizeOf()
|
||||
{
|
||||
auto ccrt = CycleCollectedJSRuntime::Get();
|
||||
MOZ_ASSERT(ccrt);
|
||||
auto rt = ccrt->Runtime();
|
||||
MOZ_ASSERT(rt);
|
||||
auto mallocSizeOf = JS::dbg::GetDebuggerMallocSizeOf(rt);
|
||||
MOZ_ASSERT(mallocSizeOf);
|
||||
return mallocSizeOf;
|
||||
}
|
||||
|
||||
dom::Nullable<uint64_t>
|
||||
DominatorTree::GetRetainedSize(uint64_t aNodeId, ErrorResult& aRv)
|
||||
{
|
||||
@ -31,7 +17,7 @@ DominatorTree::GetRetainedSize(uint64_t aNodeId, ErrorResult& aRv)
|
||||
if (node.isNothing())
|
||||
return dom::Nullable<uint64_t>();
|
||||
|
||||
auto mallocSizeOf = getCurrentThreadDebuggerMallocSizeOf();
|
||||
auto mallocSizeOf = GetCurrentThreadDebuggerMallocSizeOf();
|
||||
JS::ubi::Node::Size size = 0;
|
||||
if (!mDominatorTree.getRetainedSize(*node, mallocSizeOf, size)) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
@ -83,7 +69,7 @@ DominatorTree::GetImmediatelyDominated(uint64_t aNodeId,
|
||||
return;
|
||||
|
||||
// Get all immediately dominated nodes and their retained sizes.
|
||||
MallocSizeOf mallocSizeOf = getCurrentThreadDebuggerMallocSizeOf();
|
||||
MallocSizeOf mallocSizeOf = GetCurrentThreadDebuggerMallocSizeOf();
|
||||
Maybe<JS::ubi::DominatorTree::DominatedSetRange> range = mDominatorTree.getDominatedSet(*node);
|
||||
MOZ_ASSERT(range.isSome(), "The node should be known, since we got it from the heap snapshot.");
|
||||
size_t length = range->length();
|
||||
|
@ -55,6 +55,18 @@ using ::google::protobuf::io::ZeroCopyInputStream;
|
||||
|
||||
using JS::ubi::AtomOrTwoByteChars;
|
||||
|
||||
MallocSizeOf
|
||||
GetCurrentThreadDebuggerMallocSizeOf()
|
||||
{
|
||||
auto ccrt = CycleCollectedJSRuntime::Get();
|
||||
MOZ_ASSERT(ccrt);
|
||||
auto rt = ccrt->Runtime();
|
||||
MOZ_ASSERT(rt);
|
||||
auto mallocSizeOf = JS::dbg::GetDebuggerMallocSizeOf(rt);
|
||||
MOZ_ASSERT(mallocSizeOf);
|
||||
return mallocSizeOf;
|
||||
}
|
||||
|
||||
/*** Cycle Collection Boilerplate *****************************************************************/
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HeapSnapshot, mParent)
|
||||
@ -482,7 +494,7 @@ HeapSnapshot::TakeCensus(JSContext* cx, JS::HandleObject options,
|
||||
return;
|
||||
}
|
||||
|
||||
JS::ubi::CensusHandler handler(census, rootCount);
|
||||
JS::ubi::CensusHandler handler(census, rootCount, GetCurrentThreadDebuggerMallocSizeOf());
|
||||
|
||||
{
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
@ -504,7 +516,7 @@ HeapSnapshot::TakeCensus(JSContext* cx, JS::HandleObject options,
|
||||
}
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(!handler.report(rval))) {
|
||||
if (NS_WARN_IF(!handler.report(cx, rval))) {
|
||||
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
|
@ -221,6 +221,9 @@ WriteHeapGraph(JSContext* cx,
|
||||
ignoreNodeCount, ignoreEdgeCount);
|
||||
}
|
||||
|
||||
// Get the mozilla::MallocSizeOf for the current thread's JSRuntime.
|
||||
MallocSizeOf GetCurrentThreadDebuggerMallocSizeOf();
|
||||
|
||||
} // namespace devtools
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -6,34 +6,34 @@
|
||||
|
||||
#include "FileReader.h"
|
||||
|
||||
#include "nsContentCID.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsDOMClassInfoID.h"
|
||||
#include "nsError.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsNetCID.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsIEventTarget.h"
|
||||
#include "nsIGlobalObject.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsITransport.h"
|
||||
#include "nsIStreamTransportService.h"
|
||||
|
||||
#include "nsXPCOM.h"
|
||||
#include "nsIDOMEventListener.h"
|
||||
#include "nsJSEnvironment.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "mozilla/Base64.h"
|
||||
#include "mozilla/dom/DOMError.h"
|
||||
#include "mozilla/dom/EncodingUtils.h"
|
||||
#include "mozilla/dom/File.h"
|
||||
#include "mozilla/dom/FileReaderBinding.h"
|
||||
#include "mozilla/dom/ProgressEvent.h"
|
||||
#include "xpcpublic.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsDOMJSUtils.h"
|
||||
#include "nsError.h"
|
||||
#include "nsNetCID.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "xpcpublic.h"
|
||||
|
||||
#include "jsfriendapi.h"
|
||||
|
||||
#include "nsITransport.h"
|
||||
#include "nsIStreamTransportService.h"
|
||||
#include "WorkerPrivate.h"
|
||||
#include "WorkerScope.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
using namespace workers;
|
||||
|
||||
#define ABORT_STR "abort"
|
||||
#define LOAD_STR "load"
|
||||
#define LOADSTART_STR "loadstart"
|
||||
@ -56,7 +56,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FileReader,
|
||||
DOMEventTargetHelper)
|
||||
tmp->mResultArrayBuffer = nullptr;
|
||||
tmp->Shutdown();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlob)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressNotifier)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
|
||||
@ -76,6 +76,20 @@ NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
||||
NS_IMPL_ADDREF_INHERITED(FileReader, DOMEventTargetHelper)
|
||||
NS_IMPL_RELEASE_INHERITED(FileReader, DOMEventTargetHelper)
|
||||
|
||||
class MOZ_RAII FileReaderDecreaseBusyCounter
|
||||
{
|
||||
RefPtr<FileReader> mFileReader;
|
||||
public:
|
||||
explicit FileReaderDecreaseBusyCounter(FileReader* aFileReader)
|
||||
: mFileReader(aFileReader)
|
||||
{}
|
||||
|
||||
~FileReaderDecreaseBusyCounter()
|
||||
{
|
||||
mFileReader->DecreaseBusyCounter();
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
FileReader::RootResultArrayBuffer()
|
||||
{
|
||||
@ -84,7 +98,8 @@ FileReader::RootResultArrayBuffer()
|
||||
|
||||
//FileReader constructors/initializers
|
||||
|
||||
FileReader::FileReader(nsPIDOMWindow* aWindow)
|
||||
FileReader::FileReader(nsPIDOMWindow* aWindow,
|
||||
WorkerPrivate* aWorkerPrivate)
|
||||
: DOMEventTargetHelper(aWindow)
|
||||
, mFileData(nullptr)
|
||||
, mDataLen(0)
|
||||
@ -95,14 +110,18 @@ FileReader::FileReader(nsPIDOMWindow* aWindow)
|
||||
, mReadyState(EMPTY)
|
||||
, mTotal(0)
|
||||
, mTransferred(0)
|
||||
, mTarget(do_GetCurrentThread())
|
||||
, mBusyCount(0)
|
||||
, mWorkerPrivate(aWorkerPrivate)
|
||||
{
|
||||
MOZ_ASSERT_IF(!NS_IsMainThread(), mWorkerPrivate && !aWindow);
|
||||
MOZ_ASSERT_IF(NS_IsMainThread(), !mWorkerPrivate);
|
||||
SetDOMStringToNull(mResult);
|
||||
}
|
||||
|
||||
FileReader::~FileReader()
|
||||
{
|
||||
FreeFileData();
|
||||
mResultArrayBuffer = nullptr;
|
||||
Shutdown();
|
||||
DropJSObjects(this);
|
||||
}
|
||||
|
||||
@ -111,9 +130,17 @@ FileReader::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
|
||||
{
|
||||
// The owner can be null when this object is used by chrome code.
|
||||
nsCOMPtr<nsPIDOMWindow> owner = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
RefPtr<FileReader> fileReader = new FileReader(owner);
|
||||
WorkerPrivate* workerPrivate = nullptr;
|
||||
|
||||
if (!owner && nsContentUtils::IsCallerChrome()) {
|
||||
if (!NS_IsMainThread()) {
|
||||
JSContext* cx = aGlobal.Context();
|
||||
workerPrivate = GetWorkerPrivateFromContext(cx);
|
||||
MOZ_ASSERT(workerPrivate);
|
||||
}
|
||||
|
||||
RefPtr<FileReader> fileReader = new FileReader(owner, workerPrivate);
|
||||
|
||||
if (!owner && nsContentUtils::ThreadsafeIsCallerChrome()) {
|
||||
// Instead of grabbing some random global from the context stack,
|
||||
// let's use the default one (junk scope) for now.
|
||||
// We should move away from this Init...
|
||||
@ -215,7 +242,17 @@ FileReader::DoOnLoadEnd(nsresult aStatus,
|
||||
switch (mDataFormat) {
|
||||
case FILE_AS_ARRAYBUFFER: {
|
||||
AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(DOMEventTargetHelper::GetParentObject()))) {
|
||||
nsCOMPtr<nsIGlobalObject> globalObject;
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
globalObject = do_QueryInterface(GetParentObject());
|
||||
} else {
|
||||
MOZ_ASSERT(mWorkerPrivate);
|
||||
MOZ_ASSERT(mBusyCount);
|
||||
globalObject = mWorkerPrivate->GlobalScope();
|
||||
}
|
||||
|
||||
if (!globalObject || !jsapi.Init(globalObject)) {
|
||||
FreeFileData();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -256,9 +293,29 @@ FileReader::DoOnLoadEnd(nsresult aStatus,
|
||||
}
|
||||
|
||||
nsresult
|
||||
FileReader::DoReadData(nsIAsyncInputStream* aStream, uint64_t aCount)
|
||||
FileReader::DoAsyncWait()
|
||||
{
|
||||
MOZ_ASSERT(aStream);
|
||||
nsresult rv = IncreaseBusyCounter();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = mAsyncStream->AsyncWait(this,
|
||||
/* aFlags*/ 0,
|
||||
/* aRequestedCount */ 0,
|
||||
mTarget);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
DecreaseBusyCounter();
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
FileReader::DoReadData(uint64_t aCount)
|
||||
{
|
||||
MOZ_ASSERT(mAsyncStream);
|
||||
|
||||
if (mDataFormat == FILE_AS_BINARY) {
|
||||
//Continuously update our binary string as data comes in
|
||||
@ -271,7 +328,7 @@ FileReader::DoReadData(nsIAsyncInputStream* aStream, uint64_t aCount)
|
||||
NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
uint32_t bytesRead = 0;
|
||||
aStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
|
||||
mAsyncStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
|
||||
&bytesRead);
|
||||
NS_ASSERTION(bytesRead == aCount, "failed to read data");
|
||||
}
|
||||
@ -287,7 +344,7 @@ FileReader::DoReadData(nsIAsyncInputStream* aStream, uint64_t aCount)
|
||||
}
|
||||
|
||||
uint32_t bytesRead = 0;
|
||||
aStream->Read(mFileData + mDataLen, aCount, &bytesRead);
|
||||
mAsyncStream->Read(mFileData + mDataLen, aCount, &bytesRead);
|
||||
NS_ASSERTION(bytesRead == aCount, "failed to read data");
|
||||
}
|
||||
|
||||
@ -361,10 +418,7 @@ FileReader::ReadFileContent(Blob& aBlob,
|
||||
return;
|
||||
}
|
||||
|
||||
aRv = mAsyncStream->AsyncWait(this,
|
||||
/* aFlags*/ 0,
|
||||
/* aRequestedCount */ 0,
|
||||
NS_GetCurrentThread());
|
||||
aRv = DoAsyncWait();
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
@ -467,6 +521,7 @@ FileReader::StartProgressEventTimer()
|
||||
mProgressEventWasDelayed = false;
|
||||
mTimerIsActive = true;
|
||||
mProgressNotifier->Cancel();
|
||||
mProgressNotifier->SetTarget(mTarget);
|
||||
mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
|
||||
nsITimer::TYPE_ONE_SHOT);
|
||||
}
|
||||
@ -550,18 +605,20 @@ FileReader::OnInputStreamReady(nsIAsyncInputStream* aStream)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// We use this class to decrease the busy counter at the end of this method.
|
||||
// In theory we can do it immediatelly but, for debugging reasons, we want to
|
||||
// be 100% sure we have a feature when OnLoadEnd() is called.
|
||||
FileReaderDecreaseBusyCounter RAII(this);
|
||||
|
||||
uint64_t aCount;
|
||||
nsresult rv = aStream->Available(&aCount);
|
||||
|
||||
if (NS_SUCCEEDED(rv) && aCount) {
|
||||
rv = DoReadData(aStream, aCount);
|
||||
rv = DoReadData(aCount);
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
rv = aStream->AsyncWait(this,
|
||||
/* aFlags*/ 0,
|
||||
/* aRequestedCount */ 0,
|
||||
NS_GetCurrentThread());
|
||||
rv = DoAsyncWait();
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv) || !aCount) {
|
||||
@ -643,5 +700,56 @@ FileReader::Abort(ErrorResult& aRv)
|
||||
DispatchProgressEvent(NS_LITERAL_STRING(LOADEND_STR));
|
||||
}
|
||||
|
||||
nsresult
|
||||
FileReader::IncreaseBusyCounter()
|
||||
{
|
||||
if (mWorkerPrivate && mBusyCount++ == 0 &&
|
||||
!mWorkerPrivate->AddFeature(mWorkerPrivate->GetJSContext(), this)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
FileReader::DecreaseBusyCounter()
|
||||
{
|
||||
MOZ_ASSERT_IF(mWorkerPrivate, mBusyCount);
|
||||
if (mWorkerPrivate && --mBusyCount == 0) {
|
||||
mWorkerPrivate->RemoveFeature(mWorkerPrivate->GetJSContext(), this);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
FileReader::Notify(JSContext* aCx, Status aStatus)
|
||||
{
|
||||
MOZ_ASSERT(mWorkerPrivate);
|
||||
mWorkerPrivate->AssertIsOnWorkerThread();
|
||||
|
||||
if (aStatus > Running) {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
FileReader::Shutdown()
|
||||
{
|
||||
FreeFileData();
|
||||
mResultArrayBuffer = nullptr;
|
||||
|
||||
if (mAsyncStream) {
|
||||
mAsyncStream->Close();
|
||||
mAsyncStream = nullptr;
|
||||
}
|
||||
|
||||
if (mWorkerPrivate && mBusyCount != 0) {
|
||||
mWorkerPrivate->RemoveFeature(mWorkerPrivate->GetJSContext(), this);
|
||||
mWorkerPrivate = nullptr;
|
||||
mBusyCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
|
@ -9,37 +9,45 @@
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/DOMEventTargetHelper.h"
|
||||
#include "mozilla/dom/DOMError.h"
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIAsyncInputStream.h"
|
||||
#include "nsIStreamListener.h"
|
||||
#include "nsISupportsUtils.h"
|
||||
#include "nsIInterfaceRequestor.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsJSUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsString.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsWeakReference.h"
|
||||
#include "prtime.h"
|
||||
#include "WorkerFeature.h"
|
||||
|
||||
#define NS_PROGRESS_EVENT_INTERVAL 50
|
||||
|
||||
class nsITimer;
|
||||
class nsIEventTarget;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class Blob;
|
||||
class DOMError;
|
||||
|
||||
namespace workers {
|
||||
class WorkerPrivate;
|
||||
}
|
||||
|
||||
extern const uint64_t kUnknownSize;
|
||||
|
||||
class FileReaderDecreaseBusyCounter;
|
||||
|
||||
class FileReader final : public DOMEventTargetHelper,
|
||||
public nsIInterfaceRequestor,
|
||||
public nsSupportsWeakReference,
|
||||
public nsIInputStreamCallback,
|
||||
public nsITimerCallback
|
||||
public nsITimerCallback,
|
||||
public workers::WorkerFeature
|
||||
{
|
||||
friend class FileReaderDecreaseBusyCounter;
|
||||
|
||||
public:
|
||||
explicit FileReader(nsPIDOMWindow* aWindow);
|
||||
FileReader(nsPIDOMWindow* aWindow,
|
||||
workers::WorkerPrivate* aWorkerPrivate);
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
@ -47,9 +55,11 @@ public:
|
||||
NS_DECL_NSIINPUTSTREAMCALLBACK
|
||||
NS_DECL_NSIINTERFACEREQUESTOR
|
||||
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(FileReader, DOMEventTargetHelper)
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(FileReader,
|
||||
DOMEventTargetHelper)
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
// WebIDL
|
||||
static already_AddRefed<FileReader>
|
||||
@ -96,6 +106,9 @@ public:
|
||||
ReadFileContent(aBlob, EmptyString(), FILE_AS_BINARY, aRv);
|
||||
}
|
||||
|
||||
// WorkerFeature
|
||||
bool Notify(JSContext* aCx, workers::Status) override;
|
||||
|
||||
private:
|
||||
virtual ~FileReader();
|
||||
|
||||
@ -131,7 +144,8 @@ private:
|
||||
void DispatchError(nsresult rv, nsAString& finalEvent);
|
||||
nsresult DispatchProgressEvent(const nsAString& aType);
|
||||
|
||||
nsresult DoReadData(nsIAsyncInputStream* aStream, uint64_t aCount);
|
||||
nsresult DoAsyncWait();
|
||||
nsresult DoReadData(uint64_t aCount);
|
||||
|
||||
nsresult DoOnLoadEnd(nsresult aStatus, nsAString& aSuccessEvent,
|
||||
nsAString& aTerminationEvent);
|
||||
@ -143,6 +157,11 @@ private:
|
||||
mDataLen = 0;
|
||||
}
|
||||
|
||||
nsresult IncreaseBusyCounter();
|
||||
void DecreaseBusyCounter();
|
||||
|
||||
void Shutdown();
|
||||
|
||||
char *mFileData;
|
||||
RefPtr<Blob> mBlob;
|
||||
nsCString mCharset;
|
||||
@ -166,6 +185,13 @@ private:
|
||||
|
||||
uint64_t mTotal;
|
||||
uint64_t mTransferred;
|
||||
|
||||
nsCOMPtr<nsIEventTarget> mTarget;
|
||||
|
||||
uint64_t mBusyCount;
|
||||
|
||||
// Kept alive with a WorkerFeature.
|
||||
workers::WorkerPrivate* mWorkerPrivate;
|
||||
};
|
||||
|
||||
} // dom namespace
|
||||
|
@ -5,6 +5,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ImageEncoder.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/dom/CanvasRenderingContext2D.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/gfx/DataSurfaceHelpers.h"
|
||||
@ -13,7 +14,10 @@
|
||||
#include "mozilla/SyncRunnable.h"
|
||||
#include "mozilla/unused.h"
|
||||
#include "gfxUtils.h"
|
||||
#include "nsIThreadPool.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsXPCOMCIDInternal.h"
|
||||
#include "WorkerPrivate.h"
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
@ -69,27 +73,28 @@ GetBRGADataSourceSurfaceSync(already_AddRefed<layers::Image> aImage)
|
||||
return helper->GetDataSurfaceSafe();
|
||||
}
|
||||
|
||||
class EncodingCompleteEvent : public nsRunnable
|
||||
class EncodingCompleteEvent : public nsCancelableRunnable
|
||||
{
|
||||
virtual ~EncodingCompleteEvent() {}
|
||||
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
EncodingCompleteEvent(nsIThread* aEncoderThread,
|
||||
EncodeCompleteCallback* aEncodeCompleteCallback)
|
||||
explicit EncodingCompleteEvent(EncodeCompleteCallback* aEncodeCompleteCallback)
|
||||
: mImgSize(0)
|
||||
, mType()
|
||||
, mImgData(nullptr)
|
||||
, mEncoderThread(aEncoderThread)
|
||||
, mEncodeCompleteCallback(aEncodeCompleteCallback)
|
||||
, mFailed(false)
|
||||
{}
|
||||
{
|
||||
if (!NS_IsMainThread() && workers::GetCurrentThreadWorkerPrivate()) {
|
||||
mCreationThread = NS_GetCurrentThread();
|
||||
} else {
|
||||
NS_GetMainThread(getter_AddRefs(mCreationThread));
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!mFailed) {
|
||||
// The correct parentObject has to be set by the mEncodeCompleteCallback.
|
||||
@ -102,7 +107,6 @@ public:
|
||||
|
||||
mEncodeCompleteCallback = nullptr;
|
||||
|
||||
mEncoderThread->Shutdown();
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -118,17 +122,20 @@ public:
|
||||
mFailed = true;
|
||||
}
|
||||
|
||||
nsIThread* GetCreationThread()
|
||||
{
|
||||
return mCreationThread;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t mImgSize;
|
||||
nsAutoString mType;
|
||||
void* mImgData;
|
||||
nsCOMPtr<nsIThread> mEncoderThread;
|
||||
nsCOMPtr<nsIThread> mCreationThread;
|
||||
RefPtr<EncodeCompleteCallback> mEncodeCompleteCallback;
|
||||
bool mFailed;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED0(EncodingCompleteEvent, nsRunnable);
|
||||
|
||||
class EncodingRunnable : public nsRunnable
|
||||
{
|
||||
virtual ~EncodingRunnable() {}
|
||||
@ -207,7 +214,8 @@ public:
|
||||
} else {
|
||||
mEncodingCompleteEvent->SetMembers(imgData, imgSize, mType);
|
||||
}
|
||||
rv = NS_DispatchToMainThread(mEncodingCompleteEvent);
|
||||
rv = mEncodingCompleteEvent->GetCreationThread()->
|
||||
Dispatch(mEncodingCompleteEvent, nsIThread::DISPATCH_NORMAL);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Better to leak than to crash.
|
||||
Unused << mEncodingCompleteEvent.forget();
|
||||
@ -231,6 +239,8 @@ private:
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED0(EncodingRunnable, nsRunnable);
|
||||
|
||||
StaticRefPtr<nsIThreadPool> ImageEncoder::sThreadPool;
|
||||
|
||||
/* static */
|
||||
nsresult
|
||||
ImageEncoder::ExtractData(nsAString& aType,
|
||||
@ -262,12 +272,13 @@ ImageEncoder::ExtractDataFromLayersImageAsync(nsAString& aType,
|
||||
return NS_IMAGELIB_ERROR_NO_ENCODER;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIThread> encoderThread;
|
||||
nsresult rv = NS_NewThread(getter_AddRefs(encoderThread), nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsresult rv = EnsureThreadPool();
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
RefPtr<EncodingCompleteEvent> completeEvent =
|
||||
new EncodingCompleteEvent(encoderThread, aEncodeCallback);
|
||||
new EncodingCompleteEvent(aEncodeCallback);
|
||||
|
||||
nsIntSize size(aImage->GetSize().width, aImage->GetSize().height);
|
||||
nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType,
|
||||
@ -279,7 +290,7 @@ ImageEncoder::ExtractDataFromLayersImageAsync(nsAString& aType,
|
||||
imgIEncoder::INPUT_FORMAT_HOSTARGB,
|
||||
size,
|
||||
aUsingCustomOptions);
|
||||
return encoderThread->Dispatch(event, NS_DISPATCH_NORMAL);
|
||||
return sThreadPool->Dispatch(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
/* static */
|
||||
@ -297,12 +308,13 @@ ImageEncoder::ExtractDataAsync(nsAString& aType,
|
||||
return NS_IMAGELIB_ERROR_NO_ENCODER;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIThread> encoderThread;
|
||||
nsresult rv = NS_NewThread(getter_AddRefs(encoderThread), nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsresult rv = EnsureThreadPool();
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
RefPtr<EncodingCompleteEvent> completeEvent =
|
||||
new EncodingCompleteEvent(encoderThread, aEncodeCallback);
|
||||
new EncodingCompleteEvent(aEncodeCallback);
|
||||
|
||||
nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType,
|
||||
aOptions,
|
||||
@ -313,7 +325,7 @@ ImageEncoder::ExtractDataAsync(nsAString& aType,
|
||||
aFormat,
|
||||
aSize,
|
||||
aUsingCustomOptions);
|
||||
return encoderThread->Dispatch(event, NS_DISPATCH_NORMAL);
|
||||
return sThreadPool->Dispatch(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
/*static*/ nsresult
|
||||
@ -479,5 +491,48 @@ ImageEncoder::GetImageEncoder(nsAString& aType)
|
||||
return encoder.forget();
|
||||
}
|
||||
|
||||
/* static */
|
||||
nsresult
|
||||
ImageEncoder::EnsureThreadPool()
|
||||
{
|
||||
if (!sThreadPool) {
|
||||
nsCOMPtr<nsIThreadPool> threadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
|
||||
sThreadPool = threadPool;
|
||||
if (!NS_IsMainThread()) {
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
|
||||
ClearOnShutdown(&sThreadPool);
|
||||
}));
|
||||
} else {
|
||||
ClearOnShutdown(&sThreadPool);
|
||||
}
|
||||
|
||||
const uint32_t kThreadLimit = 2;
|
||||
const uint32_t kIdleThreadLimit = 1;
|
||||
const uint32_t kIdleThreadTimeoutMs = 30000;
|
||||
|
||||
nsresult rv = sThreadPool->SetName(NS_LITERAL_CSTRING("EncodingRunnable"));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = sThreadPool->SetThreadLimit(kThreadLimit);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = sThreadPool->SetIdleThreadLimit(kIdleThreadLimit);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = sThreadPool->SetIdleThreadTimeout(kIdleThreadTimeoutMs);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "nsSize.h"
|
||||
|
||||
class nsICanvasRenderingContextInternal;
|
||||
class nsIThreadPool;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@ -107,6 +108,11 @@ private:
|
||||
// undefined in this case.
|
||||
static already_AddRefed<imgIEncoder> GetImageEncoder(nsAString& aType);
|
||||
|
||||
static nsresult EnsureThreadPool();
|
||||
|
||||
// Thread pool for dispatching EncodingRunnable.
|
||||
static StaticRefPtr<nsIThreadPool> sThreadPool;
|
||||
|
||||
friend class EncodingRunnable;
|
||||
};
|
||||
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "mozilla/dom/SubtleCryptoBinding.h"
|
||||
#include "mozilla/dom/ToJSValue.h"
|
||||
#include "mozilla/dom/WebCryptoCommon.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/ipc/BackgroundChild.h"
|
||||
#include "mozilla/ipc/BackgroundUtils.h"
|
||||
#include "mozilla/ipc/PBackgroundSharedTypes.h"
|
||||
@ -306,7 +307,7 @@ StructuredCloneHolder::Read(nsISupports* aParent,
|
||||
// If we are tranferring something, we cannot call 'Read()' more than once.
|
||||
if (mSupportsTransferring) {
|
||||
mBlobImplArray.Clear();
|
||||
mClonedImages.Clear();
|
||||
mClonedSurfaces.Clear();
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
@ -976,7 +977,7 @@ StructuredCloneHolder::CustomReadHandler(JSContext* aCx,
|
||||
nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent);
|
||||
// aIndex is the index of the cloned image.
|
||||
return ImageBitmap::ReadStructuredClone(aCx, aReader,
|
||||
parent, GetImages(), aIndex);
|
||||
parent, GetSurfaces(), aIndex);
|
||||
}
|
||||
|
||||
return ReadFullySerializableObjects(aCx, aReader, aTag);
|
||||
@ -1021,7 +1022,7 @@ StructuredCloneHolder::CustomWriteHandler(JSContext* aCx,
|
||||
ImageBitmap* imageBitmap = nullptr;
|
||||
if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, aObj, imageBitmap))) {
|
||||
return ImageBitmap::WriteStructuredClone(aWriter,
|
||||
GetImages(),
|
||||
GetSurfaces(),
|
||||
imageBitmap);
|
||||
}
|
||||
}
|
||||
@ -1072,7 +1073,8 @@ StructuredCloneHolder::CustomReadTransferHandler(JSContext* aCx,
|
||||
MOZ_ASSERT(aContent);
|
||||
OffscreenCanvasCloneData* data =
|
||||
static_cast<OffscreenCanvasCloneData*>(aContent);
|
||||
RefPtr<OffscreenCanvas> canvas = OffscreenCanvas::CreateFromCloneData(data);
|
||||
nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent);
|
||||
RefPtr<OffscreenCanvas> canvas = OffscreenCanvas::CreateFromCloneData(parent, data);
|
||||
delete data;
|
||||
|
||||
JS::Rooted<JS::Value> value(aCx);
|
||||
@ -1085,6 +1087,26 @@ StructuredCloneHolder::CustomReadTransferHandler(JSContext* aCx,
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aTag == SCTAG_DOM_IMAGEBITMAP) {
|
||||
MOZ_ASSERT(mSupportedContext == SameProcessSameThread ||
|
||||
mSupportedContext == SameProcessDifferentThread);
|
||||
MOZ_ASSERT(aContent);
|
||||
ImageBitmapCloneData* data =
|
||||
static_cast<ImageBitmapCloneData*>(aContent);
|
||||
nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent);
|
||||
RefPtr<ImageBitmap> bitmap = ImageBitmap::CreateFromCloneData(parent, data);
|
||||
delete data;
|
||||
|
||||
JS::Rooted<JS::Value> value(aCx);
|
||||
if (!GetOrCreateDOMReflector(aCx, bitmap, &value)) {
|
||||
JS_ClearPendingException(aCx);
|
||||
return false;
|
||||
}
|
||||
|
||||
aReturnObject.set(&value.toObject());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1133,6 +1155,21 @@ StructuredCloneHolder::CustomWriteTransferHandler(JSContext* aCx,
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ImageBitmap* bitmap = nullptr;
|
||||
rv = UNWRAP_OBJECT(ImageBitmap, aObj, bitmap);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
MOZ_ASSERT(bitmap);
|
||||
|
||||
*aExtraData = 0;
|
||||
*aTag = SCTAG_DOM_IMAGEBITMAP;
|
||||
*aOwnership = JS::SCTAG_TMO_CUSTOM;
|
||||
*aContent = bitmap->ToCloneData();
|
||||
MOZ_ASSERT(*aContent);
|
||||
bitmap->Close();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1163,6 +1200,16 @@ StructuredCloneHolder::CustomFreeTransferHandler(uint32_t aTag,
|
||||
delete data;
|
||||
return;
|
||||
}
|
||||
|
||||
if (aTag == SCTAG_DOM_IMAGEBITMAP) {
|
||||
MOZ_ASSERT(mSupportedContext == SameProcessSameThread ||
|
||||
mSupportedContext == SameProcessDifferentThread);
|
||||
MOZ_ASSERT(aContent);
|
||||
ImageBitmapCloneData* data =
|
||||
static_cast<ImageBitmapCloneData*>(aContent);
|
||||
delete data;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // dom namespace
|
||||
|
@ -22,6 +22,10 @@ namespace layers {
|
||||
class Image;
|
||||
}
|
||||
|
||||
namespace gfx {
|
||||
class DataSourceSurface;
|
||||
}
|
||||
|
||||
namespace dom {
|
||||
|
||||
class StructuredCloneHolderBase
|
||||
@ -181,7 +185,7 @@ public:
|
||||
bool HasClonedDOMObjects() const
|
||||
{
|
||||
return !mBlobImplArray.IsEmpty() ||
|
||||
!mClonedImages.IsEmpty();
|
||||
!mClonedSurfaces.IsEmpty();
|
||||
}
|
||||
|
||||
nsTArray<RefPtr<BlobImpl>>& BlobImpls()
|
||||
@ -212,9 +216,9 @@ public:
|
||||
return mPortIdentifiers;
|
||||
}
|
||||
|
||||
nsTArray<RefPtr<layers::Image>>& GetImages()
|
||||
nsTArray<RefPtr<gfx::DataSourceSurface>>& GetSurfaces()
|
||||
{
|
||||
return mClonedImages;
|
||||
return mClonedSurfaces;
|
||||
}
|
||||
|
||||
// Implementations of the virtual methods to allow cloning of objects which
|
||||
@ -291,10 +295,10 @@ protected:
|
||||
nsTArray<RefPtr<BlobImpl>> mBlobImplArray;
|
||||
|
||||
// This is used for sharing the backend of ImageBitmaps.
|
||||
// The layers::Image object must be thread-safely reference-counted.
|
||||
// The layers::Image object will not be written ever via any ImageBitmap
|
||||
// The DataSourceSurface object must be thread-safely reference-counted.
|
||||
// The DataSourceSurface object will not be written ever via any ImageBitmap
|
||||
// instance, so no race condition will occur.
|
||||
nsTArray<RefPtr<layers::Image>> mClonedImages;
|
||||
nsTArray<RefPtr<gfx::DataSourceSurface>> mClonedSurfaces;
|
||||
|
||||
// This raw pointer is only set within ::Read() and is unset by the end.
|
||||
nsISupports* MOZ_NON_OWNING_REF mParent;
|
||||
|
@ -6775,66 +6775,6 @@ nsContentUtils::HaveEqualPrincipals(nsIDocument* aDoc1, nsIDocument* aDoc2)
|
||||
return principalsEqual;
|
||||
}
|
||||
|
||||
static void
|
||||
CheckForWindowedPlugins(nsISupports* aSupports, void* aResult)
|
||||
{
|
||||
nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
|
||||
if (!content || !content->IsInDoc()) {
|
||||
return;
|
||||
}
|
||||
nsCOMPtr<nsIObjectLoadingContent> olc(do_QueryInterface(content));
|
||||
if (!olc) {
|
||||
return;
|
||||
}
|
||||
RefPtr<nsNPAPIPluginInstance> plugin;
|
||||
olc->GetPluginInstance(getter_AddRefs(plugin));
|
||||
if (!plugin) {
|
||||
return;
|
||||
}
|
||||
bool isWindowless = false;
|
||||
nsresult res = plugin->IsWindowless(&isWindowless);
|
||||
if (NS_SUCCEEDED(res) && !isWindowless) {
|
||||
*static_cast<bool*>(aResult) = true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
DocTreeContainsWindowedPlugins(nsIDocument* aDoc, void* aResult)
|
||||
{
|
||||
if (!nsContentUtils::IsChromeDoc(aDoc)) {
|
||||
aDoc->EnumerateActivityObservers(CheckForWindowedPlugins, aResult);
|
||||
}
|
||||
if (*static_cast<bool*>(aResult)) {
|
||||
// Return false to stop iteration, we found a windowed plugin.
|
||||
return false;
|
||||
}
|
||||
aDoc->EnumerateSubDocuments(DocTreeContainsWindowedPlugins, aResult);
|
||||
// Return false to stop iteration if we found a windowed plugin in
|
||||
// the sub documents.
|
||||
return !*static_cast<bool*>(aResult);
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool
|
||||
nsContentUtils::HasPluginWithUncontrolledEventDispatch(nsIDocument* aDoc)
|
||||
{
|
||||
#ifdef XP_MACOSX
|
||||
// We control dispatch to all mac plugins.
|
||||
return false;
|
||||
#endif
|
||||
bool result = false;
|
||||
|
||||
// Find the top of the document's branch, the child of the chrome document.
|
||||
nsIDocument* doc = aDoc;
|
||||
nsIDocument* parent = nullptr;
|
||||
while (doc && (parent = doc->GetParentDocument()) && !IsChromeDoc(parent)) {
|
||||
doc = parent;
|
||||
}
|
||||
|
||||
DocTreeContainsWindowedPlugins(doc, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool
|
||||
nsContentUtils::HasPluginWithUncontrolledEventDispatch(nsIContent* aContent)
|
||||
@ -6842,10 +6782,30 @@ nsContentUtils::HasPluginWithUncontrolledEventDispatch(nsIContent* aContent)
|
||||
#ifdef XP_MACOSX
|
||||
// We control dispatch to all mac plugins.
|
||||
return false;
|
||||
#else
|
||||
if (!aContent || !aContent->IsInDoc()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(aContent);
|
||||
if (!olc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RefPtr<nsNPAPIPluginInstance> plugin;
|
||||
olc->GetPluginInstance(getter_AddRefs(plugin));
|
||||
if (!plugin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isWindowless = false;
|
||||
nsresult res = plugin->IsWindowless(&isWindowless);
|
||||
if (NS_FAILED(res)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !isWindowless;
|
||||
#endif
|
||||
bool result = false;
|
||||
CheckForWindowedPlugins(aContent, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
@ -2059,14 +2059,6 @@ public:
|
||||
return sPrivacyResistFingerprinting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the doc tree branch which contains aDoc contains any
|
||||
* plugins which we don't control event dispatch for, i.e. do any plugins
|
||||
* in the same tab as this document receive key events outside of our
|
||||
* control? This always returns false on MacOSX.
|
||||
*/
|
||||
static bool HasPluginWithUncontrolledEventDispatch(nsIDocument* aDoc);
|
||||
|
||||
/**
|
||||
* Return true if this doc is controlled by a ServiceWorker.
|
||||
*/
|
||||
|
@ -7668,7 +7668,6 @@ nsGlobalWindow::Open(const nsAString& aUrl, const nsAString& aName,
|
||||
false, // aDoJSFixups
|
||||
true, // aNavigate
|
||||
nullptr, nullptr, // No args
|
||||
GetPrincipal(), // aCalleePrincipal
|
||||
nullptr, // aJSCallerContext
|
||||
getter_AddRefs(window));
|
||||
if (NS_SUCCEEDED(rv) && window) {
|
||||
@ -7689,7 +7688,6 @@ nsGlobalWindow::OpenJS(const nsAString& aUrl, const nsAString& aName,
|
||||
true, // aDoJSFixups
|
||||
true, // aNavigate
|
||||
nullptr, nullptr, // No args
|
||||
GetPrincipal(), // aCalleePrincipal
|
||||
nsContentUtils::GetCurrentJSContext(), // aJSCallerContext
|
||||
_retval);
|
||||
}
|
||||
@ -7709,7 +7707,6 @@ nsGlobalWindow::OpenDialog(const nsAString& aUrl, const nsAString& aName,
|
||||
false, // aDoJSFixups
|
||||
true, // aNavigate
|
||||
nullptr, aExtraArgument, // Arguments
|
||||
GetPrincipal(), // aCalleePrincipal
|
||||
nullptr, // aJSCallerContext
|
||||
_retval);
|
||||
}
|
||||
@ -7729,7 +7726,6 @@ nsGlobalWindow::OpenNoNavigate(const nsAString& aUrl,
|
||||
false, // aDoJSFixups
|
||||
false, // aNavigate
|
||||
nullptr, nullptr, // No args
|
||||
GetPrincipal(), // aCalleePrincipal
|
||||
nullptr, // aJSCallerContext
|
||||
_retval);
|
||||
|
||||
@ -7759,7 +7755,6 @@ nsGlobalWindow::OpenDialogOuter(JSContext* aCx, const nsAString& aUrl,
|
||||
false, // aDoJSFixups
|
||||
true, // aNavigate
|
||||
argvArray, nullptr, // Arguments
|
||||
GetPrincipal(), // aCalleePrincipal
|
||||
aCx, // aJSCallerContext
|
||||
getter_AddRefs(dialog));
|
||||
return dialog.forget();
|
||||
@ -8839,7 +8834,6 @@ nsGlobalWindow::ShowModalDialogOuter(const nsAString& aUrl, nsIVariant* aArgumen
|
||||
true, // aDoJSFixups
|
||||
true, // aNavigate
|
||||
nullptr, argHolder, // args
|
||||
GetPrincipal(), // aCalleePrincipal
|
||||
nullptr, // aJSCallerContext
|
||||
getter_AddRefs(dlgWin));
|
||||
nsContentUtils::SetMicroTaskLevel(oldMicroTaskLevel);
|
||||
@ -11270,7 +11264,6 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName,
|
||||
bool aDoJSFixups, bool aNavigate,
|
||||
nsIArray *argv,
|
||||
nsISupports *aExtraArgument,
|
||||
nsIPrincipal *aCalleePrincipal,
|
||||
JSContext *aJSCallerContext,
|
||||
nsIDOMWindow **aReturn)
|
||||
{
|
||||
|
@ -1392,7 +1392,6 @@ private:
|
||||
bool aNavigate,
|
||||
nsIArray *argv,
|
||||
nsISupports *aExtraArgument,
|
||||
nsIPrincipal *aCalleePrincipal,
|
||||
JSContext *aJSCallerContext,
|
||||
nsIDOMWindow **aReturn);
|
||||
|
||||
|
@ -5219,19 +5219,17 @@ CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx,
|
||||
IntRect dstWriteRect = srcReadRect;
|
||||
dstWriteRect.MoveBy(-aX, -aY);
|
||||
|
||||
uint8_t* src;
|
||||
uint32_t srcStride;
|
||||
|
||||
if (readback) {
|
||||
srcStride = rawData.mStride;
|
||||
src = rawData.mData + srcReadRect.y * srcStride + srcReadRect.x * 4;
|
||||
}
|
||||
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
bool isShared;
|
||||
uint8_t* data = JS_GetUint8ClampedArrayData(darray, &isShared, nogc);
|
||||
MOZ_ASSERT(!isShared); // Should not happen, data was created above
|
||||
if (!readback) {
|
||||
|
||||
uint8_t* src;
|
||||
uint32_t srcStride;
|
||||
if (readback) {
|
||||
srcStride = rawData.mStride;
|
||||
src = rawData.mData + srcReadRect.y * srcStride + srcReadRect.x * 4;
|
||||
} else {
|
||||
src = data;
|
||||
srcStride = aWidth * 4;
|
||||
}
|
||||
@ -5579,9 +5577,9 @@ CanvasRenderingContext2D::GetBufferProvider(LayerManager* aManager)
|
||||
return mBufferProvider;
|
||||
}
|
||||
|
||||
already_AddRefed<CanvasLayer>
|
||||
already_AddRefed<Layer>
|
||||
CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
|
||||
CanvasLayer *aOldLayer,
|
||||
Layer *aOldLayer,
|
||||
LayerManager *aManager)
|
||||
{
|
||||
if (mOpaque || mIsSkiaGL) {
|
||||
@ -5624,8 +5622,10 @@ CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
|
||||
data.mBufferProvider = provider;
|
||||
}
|
||||
|
||||
if (userData && userData->IsForContext(this) && aOldLayer->IsDataValid(data)) {
|
||||
RefPtr<CanvasLayer> ret = aOldLayer;
|
||||
if (userData &&
|
||||
userData->IsForContext(this) &&
|
||||
static_cast<CanvasLayer*>(aOldLayer)->IsDataValid(data)) {
|
||||
RefPtr<Layer> ret = aOldLayer;
|
||||
return ret.forget();
|
||||
}
|
||||
}
|
||||
|
@ -459,8 +459,8 @@ public:
|
||||
bool GetIsOpaque() override { return mOpaque; }
|
||||
NS_IMETHOD Reset() override;
|
||||
mozilla::layers::PersistentBufferProvider* GetBufferProvider(mozilla::layers::LayerManager* aManager);
|
||||
already_AddRefed<CanvasLayer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
|
||||
CanvasLayer *aOldLayer,
|
||||
already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
|
||||
Layer *aOldLayer,
|
||||
LayerManager *aManager) override;
|
||||
virtual bool ShouldForceInactiveLayer(LayerManager *aManager) override;
|
||||
void MarkContextClean() override;
|
||||
|
@ -4,6 +4,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "CanvasRenderingContextHelper.h"
|
||||
#include "ImageBitmapRenderingContext.h"
|
||||
#include "ImageEncoder.h"
|
||||
#include "mozilla/dom/CanvasRenderingContext2D.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
@ -26,36 +27,6 @@ CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aParams,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsAutoString type;
|
||||
nsContentUtils::ASCIIToLower(aType, type);
|
||||
|
||||
nsAutoString params;
|
||||
bool usingCustomParseOptions;
|
||||
aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCurrentContext) {
|
||||
// We disallow canvases of width or height zero, and set them to 1, so
|
||||
// we will have a discrepancy with the sizes of the canvas and the context.
|
||||
// That discrepancy is OK, the rest are not.
|
||||
nsIntSize elementSize = GetWidthHeight();
|
||||
if ((elementSize.width != mCurrentContext->GetWidth() &&
|
||||
(elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) ||
|
||||
(elementSize.height != mCurrentContext->GetHeight() &&
|
||||
(elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
UniquePtr<uint8_t[]> imageBuffer;
|
||||
int32_t format = 0;
|
||||
if (mCurrentContext) {
|
||||
imageBuffer = mCurrentContext->GetImageBuffer(&format);
|
||||
}
|
||||
|
||||
// Encoder callback when encoding is complete.
|
||||
class EncodeCallback : public EncodeCompleteCallback
|
||||
{
|
||||
@ -97,6 +68,49 @@ CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
|
||||
RefPtr<EncodeCompleteCallback> callback =
|
||||
new EncodeCallback(aGlobal, &aCallback);
|
||||
|
||||
ToBlob(aCx, aGlobal, callback, aType, aParams, aRv);
|
||||
}
|
||||
|
||||
void
|
||||
CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
|
||||
nsIGlobalObject* aGlobal,
|
||||
EncodeCompleteCallback* aCallback,
|
||||
const nsAString& aType,
|
||||
JS::Handle<JS::Value> aParams,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsAutoString type;
|
||||
nsContentUtils::ASCIIToLower(aType, type);
|
||||
|
||||
nsAutoString params;
|
||||
bool usingCustomParseOptions;
|
||||
aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCurrentContext) {
|
||||
// We disallow canvases of width or height zero, and set them to 1, so
|
||||
// we will have a discrepancy with the sizes of the canvas and the context.
|
||||
// That discrepancy is OK, the rest are not.
|
||||
nsIntSize elementSize = GetWidthHeight();
|
||||
if ((elementSize.width != mCurrentContext->GetWidth() &&
|
||||
(elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) ||
|
||||
(elementSize.height != mCurrentContext->GetHeight() &&
|
||||
(elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
UniquePtr<uint8_t[]> imageBuffer;
|
||||
int32_t format = 0;
|
||||
if (mCurrentContext) {
|
||||
imageBuffer = mCurrentContext->GetImageBuffer(&format);
|
||||
}
|
||||
|
||||
RefPtr<EncodeCompleteCallback> callback = aCallback;
|
||||
|
||||
aRv = ImageEncoder::ExtractDataAsync(type,
|
||||
params,
|
||||
usingCustomParseOptions,
|
||||
@ -138,6 +152,11 @@ CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType)
|
||||
return nullptr;
|
||||
|
||||
break;
|
||||
|
||||
case CanvasContextType::ImageBitmap:
|
||||
ret = new ImageBitmapRenderingContext();
|
||||
|
||||
break;
|
||||
}
|
||||
MOZ_ASSERT(ret);
|
||||
|
||||
|
@ -18,13 +18,15 @@ class ErrorResult;
|
||||
|
||||
namespace dom {
|
||||
|
||||
class EncodeCompleteCallback;
|
||||
class FileCallback;
|
||||
|
||||
enum class CanvasContextType : uint8_t {
|
||||
NoContext,
|
||||
Canvas2D,
|
||||
WebGL1,
|
||||
WebGL2
|
||||
WebGL2,
|
||||
ImageBitmap
|
||||
};
|
||||
|
||||
/**
|
||||
@ -57,6 +59,10 @@ protected:
|
||||
const nsAString& aType, JS::Handle<JS::Value> aParams,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void ToBlob(JSContext* aCx, nsIGlobalObject* aGlobal, EncodeCompleteCallback* aCallback,
|
||||
const nsAString& aType, JS::Handle<JS::Value> aParams,
|
||||
ErrorResult& aRv);
|
||||
|
||||
virtual already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
CreateContext(CanvasContextType aContextType);
|
||||
|
||||
|
@ -61,6 +61,11 @@ GetCanvasContextType(const nsAString& str, dom::CanvasContextType* const out_typ
|
||||
}
|
||||
}
|
||||
|
||||
if (str.EqualsLiteral("bitmaprenderer")) {
|
||||
*out_type = dom::CanvasContextType::ImageBitmap;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/dom/ImageBitmap.h"
|
||||
|
||||
#include "mozilla/dom/ImageBitmapBinding.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/StructuredCloneTags.h"
|
||||
@ -145,15 +146,14 @@ CropAndCopyDataSourceSurface(DataSourceSurface* aSurface, const IntRect& aCropRe
|
||||
}
|
||||
|
||||
/*
|
||||
* Encapsulate the given _aSurface_ into a layers::CairoImage.
|
||||
* Encapsulate the given _aSurface_ into a layers::SourceSurfaceImage.
|
||||
*/
|
||||
static already_AddRefed<layers::Image>
|
||||
CreateImageFromSurface(SourceSurface* aSurface)
|
||||
{
|
||||
MOZ_ASSERT(aSurface);
|
||||
RefPtr<layers::CairoImage> image =
|
||||
new layers::CairoImage(aSurface->GetSize(), aSurface);
|
||||
|
||||
RefPtr<layers::SourceSurfaceImage> image =
|
||||
new layers::SourceSurfaceImage(aSurface->GetSize(), aSurface);
|
||||
return image.forget();
|
||||
}
|
||||
|
||||
@ -250,13 +250,13 @@ CreateImageFromRawData(const gfx::IntSize& aSize,
|
||||
|
||||
/*
|
||||
* This is a synchronous task.
|
||||
* This class is used to create a layers::CairoImage from raw data in the main
|
||||
* This class is used to create a layers::SourceSurfaceImage from raw data in the main
|
||||
* thread. While creating an ImageBitmap from an ImageData, we need to create
|
||||
* a SouceSurface from the ImageData's raw data and then set the SourceSurface
|
||||
* into a layers::CairoImage. However, the layers::CairoImage asserts the
|
||||
* into a layers::SourceSurfaceImage. However, the layers::SourceSurfaceImage asserts the
|
||||
* setting operation in the main thread, so if we are going to create an
|
||||
* ImageBitmap from an ImageData off the main thread, we post an event to the
|
||||
* main thread to create a layers::CairoImage from an ImageData's raw data.
|
||||
* main thread to create a layers::SourceSurfaceImage from an ImageData's raw data.
|
||||
*/
|
||||
class CreateImageFromRawDataInMainThreadSyncTask final :
|
||||
public WorkerMainThreadRunnable
|
||||
@ -408,6 +408,14 @@ ImageBitmap::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
return ImageBitmapBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
void
|
||||
ImageBitmap::Close()
|
||||
{
|
||||
mData = nullptr;
|
||||
mSurface = nullptr;
|
||||
mPictureRect.SetEmpty();
|
||||
}
|
||||
|
||||
void
|
||||
ImageBitmap::SetPictureRect(const IntRect& aRect, ErrorResult& aRv)
|
||||
{
|
||||
@ -419,6 +427,10 @@ ImageBitmap::PrepareForDrawTarget(gfx::DrawTarget* aTarget)
|
||||
{
|
||||
MOZ_ASSERT(aTarget);
|
||||
|
||||
if (!mData) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!mSurface) {
|
||||
mSurface = mData->GetAsSourceSurface();
|
||||
}
|
||||
@ -493,6 +505,68 @@ ImageBitmap::PrepareForDrawTarget(gfx::DrawTarget* aTarget)
|
||||
return surface.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<layers::Image>
|
||||
ImageBitmap::TransferAsImage()
|
||||
{
|
||||
RefPtr<layers::Image> image = mData;
|
||||
Close();
|
||||
return image.forget();
|
||||
}
|
||||
|
||||
ImageBitmapCloneData*
|
||||
ImageBitmap::ToCloneData()
|
||||
{
|
||||
ImageBitmapCloneData* result = new ImageBitmapCloneData();
|
||||
result->mPictureRect = mPictureRect;
|
||||
RefPtr<SourceSurface> surface = mData->GetAsSourceSurface();
|
||||
result->mSurface = surface->GetDataSurface();
|
||||
MOZ_ASSERT(result->mSurface);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<ImageBitmap>
|
||||
ImageBitmap::CreateFromCloneData(nsIGlobalObject* aGlobal,
|
||||
ImageBitmapCloneData* aData)
|
||||
{
|
||||
RefPtr<layers::Image> data =
|
||||
CreateImageFromSurface(aData->mSurface);
|
||||
|
||||
RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
|
||||
ErrorResult rv;
|
||||
ret->SetPictureRect(aData->mPictureRect, rv);
|
||||
return ret.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<ImageBitmap>
|
||||
ImageBitmap::CreateFromOffscreenCanvas(nsIGlobalObject* aGlobal,
|
||||
OffscreenCanvas& aOffscreenCanvas,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
// Check origin-clean.
|
||||
if (aOffscreenCanvas.IsWriteOnly()) {
|
||||
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsLayoutUtils::SurfaceFromElementResult res =
|
||||
nsLayoutUtils::SurfaceFromOffscreenCanvas(&aOffscreenCanvas,
|
||||
nsLayoutUtils::SFE_WANT_FIRST_FRAME);
|
||||
|
||||
RefPtr<SourceSurface> surface = res.GetSourceSurface();
|
||||
|
||||
if (NS_WARN_IF(!surface)) {
|
||||
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<layers::Image> data =
|
||||
CreateImageFromSurface(surface);
|
||||
|
||||
RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
|
||||
return ret.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<ImageBitmap>
|
||||
ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl,
|
||||
const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
|
||||
@ -1154,7 +1228,7 @@ ImageBitmap::Create(nsIGlobalObject* aGlobal, const ImageBitmapSource& aSrc,
|
||||
ImageBitmap::ReadStructuredClone(JSContext* aCx,
|
||||
JSStructuredCloneReader* aReader,
|
||||
nsIGlobalObject* aParent,
|
||||
const nsTArray<RefPtr<layers::Image>>& aClonedImages,
|
||||
const nsTArray<RefPtr<DataSourceSurface>>& aClonedSurfaces,
|
||||
uint32_t aIndex)
|
||||
{
|
||||
MOZ_ASSERT(aCx);
|
||||
@ -1177,8 +1251,8 @@ ImageBitmap::ReadStructuredClone(JSContext* aCx,
|
||||
int32_t picRectHeight = BitwiseCast<int32_t>(picRectHeight_);
|
||||
|
||||
// Create a new ImageBitmap.
|
||||
MOZ_ASSERT(!aClonedImages.IsEmpty());
|
||||
MOZ_ASSERT(aIndex < aClonedImages.Length());
|
||||
MOZ_ASSERT(!aClonedSurfaces.IsEmpty());
|
||||
MOZ_ASSERT(aIndex < aClonedSurfaces.Length());
|
||||
|
||||
// RefPtr<ImageBitmap> needs to go out of scope before toObjectOrNull() is
|
||||
// called because the static analysis thinks dereferencing XPCOM objects
|
||||
@ -1187,8 +1261,9 @@ ImageBitmap::ReadStructuredClone(JSContext* aCx,
|
||||
// while destructors are running.
|
||||
JS::Rooted<JS::Value> value(aCx);
|
||||
{
|
||||
RefPtr<layers::Image> img = CreateImageFromSurface(aClonedSurfaces[aIndex]);
|
||||
RefPtr<ImageBitmap> imageBitmap =
|
||||
new ImageBitmap(aParent, aClonedImages[aIndex]);
|
||||
new ImageBitmap(aParent, img);
|
||||
|
||||
ErrorResult error;
|
||||
imageBitmap->SetPictureRect(IntRect(picRectX, picRectY,
|
||||
@ -1208,7 +1283,7 @@ ImageBitmap::ReadStructuredClone(JSContext* aCx,
|
||||
|
||||
/*static*/ bool
|
||||
ImageBitmap::WriteStructuredClone(JSStructuredCloneWriter* aWriter,
|
||||
nsTArray<RefPtr<layers::Image>>& aClonedImages,
|
||||
nsTArray<RefPtr<DataSourceSurface>>& aClonedSurfaces,
|
||||
ImageBitmap* aImageBitmap)
|
||||
{
|
||||
MOZ_ASSERT(aWriter);
|
||||
@ -1219,8 +1294,8 @@ ImageBitmap::WriteStructuredClone(JSStructuredCloneWriter* aWriter,
|
||||
const uint32_t picRectWidth = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.width);
|
||||
const uint32_t picRectHeight = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.height);
|
||||
|
||||
// Indexing the cloned images and send the index to the receiver.
|
||||
uint32_t index = aClonedImages.Length();
|
||||
// Indexing the cloned surfaces and send the index to the receiver.
|
||||
uint32_t index = aClonedSurfaces.Length();
|
||||
|
||||
if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, SCTAG_DOM_IMAGEBITMAP, index)) ||
|
||||
NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectX, picRectY)) ||
|
||||
@ -1228,8 +1303,24 @@ ImageBitmap::WriteStructuredClone(JSStructuredCloneWriter* aWriter,
|
||||
return false;
|
||||
}
|
||||
|
||||
aClonedImages.AppendElement(aImageBitmap->mData);
|
||||
|
||||
RefPtr<SourceSurface> surface =
|
||||
aImageBitmap->mData->GetAsSourceSurface();
|
||||
RefPtr<DataSourceSurface> snapshot = surface->GetDataSurface();
|
||||
RefPtr<DataSourceSurface> dstDataSurface;
|
||||
{
|
||||
// DataSourceSurfaceD2D1::GetStride() will call EnsureMapped implicitly and
|
||||
// won't Unmap after exiting function. So instead calling GetStride()
|
||||
// directly, using ScopedMap to get stride.
|
||||
DataSourceSurface::ScopedMap map(snapshot, DataSourceSurface::READ);
|
||||
dstDataSurface =
|
||||
Factory::CreateDataSourceSurfaceWithStride(snapshot->GetSize(),
|
||||
snapshot->GetFormat(),
|
||||
map.GetStride(),
|
||||
true);
|
||||
}
|
||||
MOZ_ASSERT(dstDataSurface);
|
||||
Factory::CopyDataSourceSurface(snapshot, dstDataSurface);
|
||||
aClonedSurfaces.AppendElement(dstDataSurface);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ namespace mozilla {
|
||||
class ErrorResult;
|
||||
|
||||
namespace gfx {
|
||||
class DataSourceSurface;
|
||||
class SourceSurface;
|
||||
}
|
||||
|
||||
@ -33,6 +34,7 @@ class Image;
|
||||
}
|
||||
|
||||
namespace dom {
|
||||
class OffscreenCanvas;
|
||||
|
||||
namespace workers {
|
||||
class WorkerStructuredCloneClosure;
|
||||
@ -50,6 +52,12 @@ class CreateImageBitmapFromBlob;
|
||||
class CreateImageBitmapFromBlobTask;
|
||||
class CreateImageBitmapFromBlobWorkerTask;
|
||||
|
||||
struct ImageBitmapCloneData final
|
||||
{
|
||||
RefPtr<gfx::DataSourceSurface> mSurface;
|
||||
gfx::IntRect mPictureRect;
|
||||
};
|
||||
|
||||
/*
|
||||
* ImageBitmap is an opaque handler to several kinds of image-like objects from
|
||||
* HTMLImageElement, HTMLVideoElement, HTMLCanvasElement, ImageData to
|
||||
@ -83,6 +91,8 @@ public:
|
||||
return mPictureRect.Height();
|
||||
}
|
||||
|
||||
void Close();
|
||||
|
||||
/*
|
||||
* The PrepareForDrawTarget() might return null if the mPictureRect does not
|
||||
* intersect with the size of mData.
|
||||
@ -90,6 +100,24 @@ public:
|
||||
already_AddRefed<gfx::SourceSurface>
|
||||
PrepareForDrawTarget(gfx::DrawTarget* aTarget);
|
||||
|
||||
/*
|
||||
* Transfer ownership of buffer to caller. So this function call
|
||||
* Close() implicitly.
|
||||
*/
|
||||
already_AddRefed<layers::Image>
|
||||
TransferAsImage();
|
||||
|
||||
ImageBitmapCloneData*
|
||||
ToCloneData();
|
||||
|
||||
static already_AddRefed<ImageBitmap>
|
||||
CreateFromCloneData(nsIGlobalObject* aGlobal, ImageBitmapCloneData* aData);
|
||||
|
||||
static already_AddRefed<ImageBitmap>
|
||||
CreateFromOffscreenCanvas(nsIGlobalObject* aGlobal,
|
||||
OffscreenCanvas& aOffscreenCanvas,
|
||||
ErrorResult& aRv);
|
||||
|
||||
static already_AddRefed<Promise>
|
||||
Create(nsIGlobalObject* aGlobal, const ImageBitmapSource& aSrc,
|
||||
const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv);
|
||||
@ -98,12 +126,12 @@ public:
|
||||
ReadStructuredClone(JSContext* aCx,
|
||||
JSStructuredCloneReader* aReader,
|
||||
nsIGlobalObject* aParent,
|
||||
const nsTArray<RefPtr<layers::Image>>& aClonedImages,
|
||||
const nsTArray<RefPtr<gfx::DataSourceSurface>>& aClonedSurfaces,
|
||||
uint32_t aIndex);
|
||||
|
||||
static bool
|
||||
WriteStructuredClone(JSStructuredCloneWriter* aWriter,
|
||||
nsTArray<RefPtr<layers::Image>>& aClonedImages,
|
||||
nsTArray<RefPtr<gfx::DataSourceSurface>>& aClonedSurfaces,
|
||||
ImageBitmap* aImageBitmap);
|
||||
|
||||
friend CreateImageBitmapFromBlob;
|
||||
|
303
dom/canvas/ImageBitmapRenderingContext.cpp
Normal file
303
dom/canvas/ImageBitmapRenderingContext.cpp
Normal file
@ -0,0 +1,303 @@
|
||||
/* -*- Mode: C++; 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 "ImageBitmapRenderingContext.h"
|
||||
#include "mozilla/dom/ImageBitmapRenderingContextBinding.h"
|
||||
#include "ImageContainer.h"
|
||||
#include "ImageLayers.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
ImageBitmapRenderingContext::ImageBitmapRenderingContext()
|
||||
: mWidth(0)
|
||||
, mHeight(0)
|
||||
{
|
||||
}
|
||||
|
||||
ImageBitmapRenderingContext::~ImageBitmapRenderingContext()
|
||||
{
|
||||
RemovePostRefreshObserver();
|
||||
}
|
||||
|
||||
JSObject*
|
||||
ImageBitmapRenderingContext::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
{
|
||||
return ImageBitmapRenderingContextBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
already_AddRefed<layers::Image>
|
||||
ImageBitmapRenderingContext::ClipToIntrinsicSize()
|
||||
{
|
||||
if (!mImage) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If image is larger than canvas intrinsic size, clip it to the intrinsic size.
|
||||
RefPtr<gfx::SourceSurface> surface;
|
||||
RefPtr<layers::Image> result;
|
||||
if (mWidth < mImage->GetSize().width ||
|
||||
mHeight < mImage->GetSize().height) {
|
||||
surface = MatchWithIntrinsicSize();
|
||||
} else {
|
||||
surface = mImage->GetAsSourceSurface();
|
||||
}
|
||||
result = new layers::SourceSurfaceImage(gfx::IntSize(mWidth, mHeight), surface);
|
||||
return result.forget();
|
||||
}
|
||||
|
||||
void
|
||||
ImageBitmapRenderingContext::TransferImageBitmap(ImageBitmap& aImageBitmap)
|
||||
{
|
||||
Reset();
|
||||
mImage = aImageBitmap.TransferAsImage();
|
||||
|
||||
if (!mImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
Redraw(gfxRect(0, 0, mWidth, mHeight));
|
||||
}
|
||||
|
||||
int32_t
|
||||
ImageBitmapRenderingContext::GetWidth() const
|
||||
{
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
int32_t
|
||||
ImageBitmapRenderingContext::GetHeight() const
|
||||
{
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageBitmapRenderingContext::SetDimensions(int32_t aWidth, int32_t aHeight)
|
||||
{
|
||||
mWidth = aWidth;
|
||||
mHeight = aHeight;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageBitmapRenderingContext::InitializeWithSurface(nsIDocShell* aDocShell,
|
||||
gfxASurface* aSurface,
|
||||
int32_t aWidth,
|
||||
int32_t aHeight)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
already_AddRefed<DataSourceSurface>
|
||||
ImageBitmapRenderingContext::MatchWithIntrinsicSize()
|
||||
{
|
||||
RefPtr<SourceSurface> surface = mImage->GetAsSourceSurface();
|
||||
RefPtr<DataSourceSurface> temp =
|
||||
Factory::CreateDataSourceSurface(IntSize(mWidth, mHeight), surface->GetFormat());
|
||||
if (!temp) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DataSourceSurface::ScopedMap map(temp, DataSourceSurface::READ_WRITE);
|
||||
if (!map.IsMapped()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<DrawTarget> dt =
|
||||
Factory::CreateDrawTargetForData(BackendType::CAIRO,
|
||||
map.GetData(),
|
||||
temp->GetSize(),
|
||||
map.GetStride(),
|
||||
temp->GetFormat());
|
||||
if (!dt) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
dt->ClearRect(Rect(0, 0, mWidth, mHeight));
|
||||
dt->CopySurface(surface,
|
||||
IntRect(0, 0, surface->GetSize().width,
|
||||
surface->GetSize().height),
|
||||
IntPoint(0, 0));
|
||||
|
||||
return temp.forget();
|
||||
}
|
||||
|
||||
mozilla::UniquePtr<uint8_t[]>
|
||||
ImageBitmapRenderingContext::GetImageBuffer(int32_t* aFormat)
|
||||
{
|
||||
*aFormat = 0;
|
||||
|
||||
if (!mImage) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<SourceSurface> surface = mImage->GetAsSourceSurface();
|
||||
RefPtr<DataSourceSurface> data = surface->GetDataSurface();
|
||||
if (!data) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (data->GetSize() != IntSize(mWidth, mHeight)) {
|
||||
data = MatchWithIntrinsicSize();
|
||||
}
|
||||
|
||||
*aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB;
|
||||
return SurfaceToPackedBGRA(data);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageBitmapRenderingContext::GetInputStream(const char* aMimeType,
|
||||
const char16_t* aEncoderOptions,
|
||||
nsIInputStream** aStream)
|
||||
{
|
||||
nsCString enccid("@mozilla.org/image/encoder;2?type=");
|
||||
enccid += aMimeType;
|
||||
nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
|
||||
if (!encoder) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
int32_t format = 0;
|
||||
UniquePtr<uint8_t[]> imageBuffer = GetImageBuffer(&format);
|
||||
if (!imageBuffer) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer.get(), format,
|
||||
encoder, aEncoderOptions, aStream);
|
||||
}
|
||||
|
||||
already_AddRefed<mozilla::gfx::SourceSurface>
|
||||
ImageBitmapRenderingContext::GetSurfaceSnapshot(bool* aPremultAlpha)
|
||||
{
|
||||
if (!mImage) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (aPremultAlpha) {
|
||||
*aPremultAlpha = true;
|
||||
}
|
||||
|
||||
RefPtr<SourceSurface> surface = mImage->GetAsSourceSurface();
|
||||
if (surface->GetSize() != IntSize(mWidth, mHeight)) {
|
||||
return MatchWithIntrinsicSize();
|
||||
}
|
||||
|
||||
return surface.forget();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageBitmapRenderingContext::SetIsOpaque(bool aIsOpaque)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
ImageBitmapRenderingContext::GetIsOpaque()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageBitmapRenderingContext::Reset()
|
||||
{
|
||||
if (mCanvasElement) {
|
||||
mCanvasElement->InvalidateCanvas();
|
||||
}
|
||||
|
||||
mImage = nullptr;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
already_AddRefed<Layer>
|
||||
ImageBitmapRenderingContext::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
|
||||
Layer* aOldLayer,
|
||||
LayerManager* aManager)
|
||||
{
|
||||
if (!mImage) {
|
||||
// No DidTransactionCallback will be received, so mark the context clean
|
||||
// now so future invalidations will be dispatched.
|
||||
MarkContextClean();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<ImageLayer> imageLayer;
|
||||
|
||||
if (aOldLayer) {
|
||||
imageLayer = static_cast<ImageLayer*>(aOldLayer);
|
||||
} else {
|
||||
imageLayer = aManager->CreateImageLayer();
|
||||
}
|
||||
|
||||
RefPtr<ImageContainer> imageContainer = imageLayer->GetContainer();
|
||||
if (!imageContainer) {
|
||||
imageContainer = aManager->CreateImageContainer();
|
||||
imageLayer->SetContainer(imageContainer);
|
||||
}
|
||||
|
||||
nsAutoTArray<ImageContainer::NonOwningImage, 1> imageList;
|
||||
RefPtr<layers::Image> image = ClipToIntrinsicSize();
|
||||
imageList.AppendElement(ImageContainer::NonOwningImage(image));
|
||||
imageContainer->SetCurrentImages(imageList);
|
||||
|
||||
return imageLayer.forget();
|
||||
}
|
||||
|
||||
void
|
||||
ImageBitmapRenderingContext::MarkContextClean()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageBitmapRenderingContext::Redraw(const gfxRect& aDirty)
|
||||
{
|
||||
if (!mCanvasElement) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mozilla::gfx::Rect rect = ToRect(aDirty);
|
||||
mCanvasElement->InvalidateCanvasContent(&rect);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageBitmapRenderingContext::SetIsIPC(bool aIsIPC)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
ImageBitmapRenderingContext::DidRefresh()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ImageBitmapRenderingContext::MarkContextCleanForFrameCapture()
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
ImageBitmapRenderingContext::IsContextCleanForFrameCapture()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(ImageBitmapRenderingContext)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(ImageBitmapRenderingContext)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ImageBitmapRenderingContext,
|
||||
mCanvasElement,
|
||||
mOffscreenCanvas)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImageBitmapRenderingContext)
|
||||
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
||||
NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
}
|
||||
}
|
97
dom/canvas/ImageBitmapRenderingContext.h
Normal file
97
dom/canvas/ImageBitmapRenderingContext.h
Normal file
@ -0,0 +1,97 @@
|
||||
/* 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 ImageBitmapRenderingContext_h
|
||||
#define ImageBitmapRenderingContext_h
|
||||
|
||||
#include "nsICanvasRenderingContextInternal.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace gfx {
|
||||
class DataSourceSurface;
|
||||
class SourceSurface;
|
||||
}
|
||||
|
||||
namespace layers {
|
||||
class Image;
|
||||
class ImageContainer;
|
||||
}
|
||||
|
||||
namespace dom {
|
||||
|
||||
/**
|
||||
* The purpose of ImageBitmapRenderingContext is to provide a faster and efficient
|
||||
* way to display ImageBitmap. Simply call TransferImageBitmap() then we'll transfer
|
||||
* the surface of ImageBitmap to this context and then to use it to display.
|
||||
*
|
||||
* See more details in spec: https://wiki.whatwg.org/wiki/OffscreenCanvas
|
||||
*/
|
||||
class ImageBitmapRenderingContext final :
|
||||
public nsICanvasRenderingContextInternal,
|
||||
public nsWrapperCache
|
||||
{
|
||||
virtual ~ImageBitmapRenderingContext();
|
||||
|
||||
public:
|
||||
ImageBitmapRenderingContext();
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
// nsISupports interface + CC
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ImageBitmapRenderingContext)
|
||||
|
||||
void TransferImageBitmap(ImageBitmap& aImageBitmap);
|
||||
|
||||
// nsICanvasRenderingContextInternal
|
||||
virtual int32_t GetWidth() const override;
|
||||
virtual int32_t GetHeight() const override;
|
||||
|
||||
NS_IMETHOD SetDimensions(int32_t aWidth, int32_t aHeight) override;
|
||||
|
||||
NS_IMETHOD InitializeWithSurface(nsIDocShell* aDocShell,
|
||||
gfxASurface* aSurface,
|
||||
int32_t aWidth,
|
||||
int32_t aHeight) override;
|
||||
|
||||
virtual mozilla::UniquePtr<uint8_t[]> GetImageBuffer(int32_t* aFormat) override;
|
||||
NS_IMETHOD GetInputStream(const char* aMimeType,
|
||||
const char16_t* aEncoderOptions,
|
||||
nsIInputStream** aStream) override;
|
||||
|
||||
virtual already_AddRefed<mozilla::gfx::SourceSurface>
|
||||
GetSurfaceSnapshot(bool* aPremultAlpha = nullptr) override;
|
||||
|
||||
NS_IMETHOD SetIsOpaque(bool aIsOpaque) override;
|
||||
virtual bool GetIsOpaque() override;
|
||||
NS_IMETHOD Reset() override;
|
||||
virtual already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
|
||||
Layer* aOldLayer,
|
||||
LayerManager* aManager) override;
|
||||
virtual void MarkContextClean() override;
|
||||
|
||||
NS_IMETHOD Redraw(const gfxRect& aDirty) override;
|
||||
NS_IMETHOD SetIsIPC(bool aIsIPC) override;
|
||||
|
||||
virtual void DidRefresh() override;
|
||||
|
||||
virtual void MarkContextCleanForFrameCapture() override;
|
||||
virtual bool IsContextCleanForFrameCapture() override;
|
||||
|
||||
protected:
|
||||
already_AddRefed<gfx::DataSourceSurface> MatchWithIntrinsicSize();
|
||||
already_AddRefed<layers::Image> ClipToIntrinsicSize();
|
||||
int32_t mWidth;
|
||||
int32_t mHeight;
|
||||
|
||||
RefPtr<layers::Image> mImage;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ImageBitmapRenderingContext_h */
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "mozilla/dom/OffscreenCanvasBinding.h"
|
||||
#include "mozilla/dom/WorkerPrivate.h"
|
||||
#include "mozilla/dom/WorkerScope.h"
|
||||
#include "mozilla/layers/AsyncCanvasRenderer.h"
|
||||
#include "mozilla/layers/CanvasClient.h"
|
||||
#include "mozilla/layers/ImageBridgeChild.h"
|
||||
@ -24,12 +25,13 @@ namespace dom {
|
||||
OffscreenCanvasCloneData::OffscreenCanvasCloneData(layers::AsyncCanvasRenderer* aRenderer,
|
||||
uint32_t aWidth, uint32_t aHeight,
|
||||
layers::LayersBackend aCompositorBackend,
|
||||
bool aNeutered)
|
||||
bool aNeutered, bool aIsWriteOnly)
|
||||
: mRenderer(aRenderer)
|
||||
, mWidth(aWidth)
|
||||
, mHeight(aHeight)
|
||||
, mCompositorBackendType(aCompositorBackend)
|
||||
, mNeutered(aNeutered)
|
||||
, mIsWriteOnly(aIsWriteOnly)
|
||||
{
|
||||
}
|
||||
|
||||
@ -37,12 +39,15 @@ OffscreenCanvasCloneData::~OffscreenCanvasCloneData()
|
||||
{
|
||||
}
|
||||
|
||||
OffscreenCanvas::OffscreenCanvas(uint32_t aWidth,
|
||||
OffscreenCanvas::OffscreenCanvas(nsIGlobalObject* aGlobal,
|
||||
uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
layers::LayersBackend aCompositorBackend,
|
||||
layers::AsyncCanvasRenderer* aRenderer)
|
||||
: mAttrDirty(false)
|
||||
: DOMEventTargetHelper(aGlobal)
|
||||
, mAttrDirty(false)
|
||||
, mNeutered(false)
|
||||
, mIsWriteOnly(false)
|
||||
, mWidth(aWidth)
|
||||
, mHeight(aHeight)
|
||||
, mCompositorBackendType(aCompositorBackend)
|
||||
@ -55,12 +60,6 @@ OffscreenCanvas::~OffscreenCanvas()
|
||||
ClearResources();
|
||||
}
|
||||
|
||||
OffscreenCanvas*
|
||||
OffscreenCanvas::GetParentObject() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSObject*
|
||||
OffscreenCanvas::WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto)
|
||||
@ -68,6 +67,19 @@ OffscreenCanvas::WrapObject(JSContext* aCx,
|
||||
return OffscreenCanvasBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<OffscreenCanvas>
|
||||
OffscreenCanvas::Constructor(const GlobalObject& aGlobal,
|
||||
uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
RefPtr<OffscreenCanvas> offscreenCanvas =
|
||||
new OffscreenCanvas(global, aWidth, aHeight,
|
||||
layers::LayersBackend::LAYERS_NONE, nullptr);
|
||||
return offscreenCanvas.forget();
|
||||
}
|
||||
|
||||
void
|
||||
OffscreenCanvas::ClearResources()
|
||||
{
|
||||
@ -79,7 +91,9 @@ OffscreenCanvas::ClearResources()
|
||||
if (mCanvasRenderer) {
|
||||
nsCOMPtr<nsIThread> activeThread = mCanvasRenderer->GetActiveThread();
|
||||
MOZ_RELEASE_ASSERT(activeThread);
|
||||
MOZ_RELEASE_ASSERT(activeThread == NS_GetCurrentThread());
|
||||
bool current;
|
||||
activeThread->IsOnCurrentThread(¤t);
|
||||
MOZ_RELEASE_ASSERT(current);
|
||||
mCanvasRenderer->SetCanvasClient(nullptr);
|
||||
mCanvasRenderer->mContext = nullptr;
|
||||
mCanvasRenderer->mGLContext = nullptr;
|
||||
@ -107,7 +121,8 @@ OffscreenCanvas::GetContext(JSContext* aCx,
|
||||
}
|
||||
|
||||
if (!(contextType == CanvasContextType::WebGL1 ||
|
||||
contextType == CanvasContextType::WebGL2))
|
||||
contextType == CanvasContextType::WebGL2 ||
|
||||
contextType == CanvasContextType::ImageBitmap))
|
||||
{
|
||||
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
|
||||
return nullptr;
|
||||
@ -124,6 +139,8 @@ OffscreenCanvas::GetContext(JSContext* aCx,
|
||||
}
|
||||
|
||||
if (mCanvasRenderer) {
|
||||
if (contextType == CanvasContextType::WebGL1 ||
|
||||
contextType == CanvasContextType::WebGL2) {
|
||||
WebGLContext* webGL = static_cast<WebGLContext*>(mCurrentContext.get());
|
||||
gl::GLContext* gl = webGL->GL();
|
||||
mCanvasRenderer->mContext = mCurrentContext;
|
||||
@ -148,6 +165,7 @@ OffscreenCanvas::GetContext(JSContext* aCx,
|
||||
screen->Morph(Move(factory));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -165,6 +183,12 @@ OffscreenCanvas::CreateContext(CanvasContextType aContextType)
|
||||
void
|
||||
OffscreenCanvas::CommitFrameToCompositor()
|
||||
{
|
||||
if (!mCanvasRenderer) {
|
||||
// This offscreen canvas doesn't associate to any HTML canvas element.
|
||||
// So, just bail out.
|
||||
return;
|
||||
}
|
||||
|
||||
// The attributes has changed, we have to notify main
|
||||
// thread to change canvas size.
|
||||
if (mAttrDirty) {
|
||||
@ -191,15 +215,121 @@ OffscreenCanvasCloneData*
|
||||
OffscreenCanvas::ToCloneData()
|
||||
{
|
||||
return new OffscreenCanvasCloneData(mCanvasRenderer, mWidth, mHeight,
|
||||
mCompositorBackendType, mNeutered);
|
||||
mCompositorBackendType, mNeutered, mIsWriteOnly);
|
||||
}
|
||||
|
||||
already_AddRefed<ImageBitmap>
|
||||
OffscreenCanvas::TransferToImageBitmap()
|
||||
{
|
||||
ErrorResult rv;
|
||||
RefPtr<ImageBitmap> result = ImageBitmap::CreateFromOffscreenCanvas(GetGlobalObject(), *this, rv);
|
||||
|
||||
// Clear the content.
|
||||
if ((mCurrentContextType == CanvasContextType::WebGL1 ||
|
||||
mCurrentContextType == CanvasContextType::WebGL2))
|
||||
{
|
||||
WebGLContext* webGL = static_cast<WebGLContext*>(mCurrentContext.get());
|
||||
webGL->ClearScreen();
|
||||
}
|
||||
|
||||
return result.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
OffscreenCanvas::ToBlob(JSContext* aCx,
|
||||
const nsAString& aType,
|
||||
JS::Handle<JS::Value> aParams,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
// do a trust check if this is a write-only canvas
|
||||
if (mIsWriteOnly) {
|
||||
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> global = GetGlobalObject();
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(global, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Encoder callback when encoding is complete.
|
||||
class EncodeCallback : public EncodeCompleteCallback
|
||||
{
|
||||
public:
|
||||
EncodeCallback(nsIGlobalObject* aGlobal, Promise* aPromise)
|
||||
: mGlobal(aGlobal)
|
||||
, mPromise(aPromise) {}
|
||||
|
||||
// This is called on main thread.
|
||||
nsresult ReceiveBlob(already_AddRefed<Blob> aBlob)
|
||||
{
|
||||
RefPtr<Blob> blob = aBlob;
|
||||
|
||||
ErrorResult rv;
|
||||
uint64_t size = blob->GetSize(rv);
|
||||
if (rv.Failed()) {
|
||||
rv.SuppressException();
|
||||
} else {
|
||||
AutoJSAPI jsapi;
|
||||
if (jsapi.Init(mGlobal)) {
|
||||
JS_updateMallocCounter(jsapi.cx(), size);
|
||||
}
|
||||
}
|
||||
|
||||
if (mPromise) {
|
||||
RefPtr<Blob> newBlob = Blob::Create(mGlobal, blob->Impl());
|
||||
mPromise->MaybeResolve(newBlob);
|
||||
}
|
||||
|
||||
mGlobal = nullptr;
|
||||
mPromise = nullptr;
|
||||
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
RefPtr<Promise> mPromise;
|
||||
};
|
||||
|
||||
RefPtr<EncodeCompleteCallback> callback =
|
||||
new EncodeCallback(global, promise);
|
||||
|
||||
CanvasRenderingContextHelper::ToBlob(aCx, global,
|
||||
callback, aType, aParams, aRv);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<gfx::SourceSurface>
|
||||
OffscreenCanvas::GetSurfaceSnapshot(bool* aPremultAlpha)
|
||||
{
|
||||
if (!mCurrentContext) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return mCurrentContext->GetSurfaceSnapshot(aPremultAlpha);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIGlobalObject>
|
||||
OffscreenCanvas::GetGlobalObject()
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
return GetParentObject();
|
||||
}
|
||||
|
||||
dom::workers::WorkerPrivate* workerPrivate =
|
||||
dom::workers::GetCurrentThreadWorkerPrivate();
|
||||
return workerPrivate->GlobalScope();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<OffscreenCanvas>
|
||||
OffscreenCanvas::CreateFromCloneData(OffscreenCanvasCloneData* aData)
|
||||
OffscreenCanvas::CreateFromCloneData(nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData)
|
||||
{
|
||||
MOZ_ASSERT(aData);
|
||||
RefPtr<OffscreenCanvas> wc =
|
||||
new OffscreenCanvas(aData->mWidth, aData->mHeight,
|
||||
new OffscreenCanvas(aGlobal, aData->mWidth, aData->mHeight,
|
||||
aData->mCompositorBackendType, aData->mRenderer);
|
||||
if (aData->mNeutered) {
|
||||
wc->SetNeutered();
|
||||
|
@ -25,6 +25,8 @@ class CanvasClient;
|
||||
} // namespace layers
|
||||
|
||||
namespace dom {
|
||||
class Blob;
|
||||
class ImageBitmap;
|
||||
|
||||
// This is helper class for transferring OffscreenCanvas to worker thread.
|
||||
// Because OffscreenCanvas is not thread-safe. So we cannot pass Offscreen-
|
||||
@ -35,7 +37,7 @@ struct OffscreenCanvasCloneData final
|
||||
OffscreenCanvasCloneData(layers::AsyncCanvasRenderer* aRenderer,
|
||||
uint32_t aWidth, uint32_t aHeight,
|
||||
layers::LayersBackend aCompositorBackend,
|
||||
bool aNeutered);
|
||||
bool aNeutered, bool aIsWriteOnly);
|
||||
~OffscreenCanvasCloneData();
|
||||
|
||||
RefPtr<layers::AsyncCanvasRenderer> mRenderer;
|
||||
@ -43,6 +45,7 @@ struct OffscreenCanvasCloneData final
|
||||
uint32_t mHeight;
|
||||
layers::LayersBackend mCompositorBackendType;
|
||||
bool mNeutered;
|
||||
bool mIsWriteOnly;
|
||||
};
|
||||
|
||||
class OffscreenCanvas final : public DOMEventTargetHelper
|
||||
@ -52,16 +55,23 @@ public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(OffscreenCanvas, DOMEventTargetHelper)
|
||||
|
||||
OffscreenCanvas(uint32_t aWidth,
|
||||
OffscreenCanvas(nsIGlobalObject* aGlobal,
|
||||
uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
layers::LayersBackend aCompositorBackend,
|
||||
layers::AsyncCanvasRenderer* aRenderer);
|
||||
|
||||
OffscreenCanvas* GetParentObject() const;
|
||||
nsCOMPtr<nsIGlobalObject> GetParentObject() const { return GetOwnerGlobal(); }
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
static already_AddRefed<OffscreenCanvas>
|
||||
Constructor(const GlobalObject& aGlobal,
|
||||
uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void ClearResources();
|
||||
|
||||
uint32_t Width() const
|
||||
@ -100,13 +110,24 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<ImageBitmap>
|
||||
TransferToImageBitmap();
|
||||
|
||||
already_AddRefed<Promise>
|
||||
ToBlob(JSContext* aCx,
|
||||
const nsAString& aType,
|
||||
JS::Handle<JS::Value> aParams,
|
||||
ErrorResult& aRv);
|
||||
|
||||
nsICanvasRenderingContextInternal* GetContext() const
|
||||
{
|
||||
return mCurrentContext;
|
||||
}
|
||||
|
||||
already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr);
|
||||
|
||||
static already_AddRefed<OffscreenCanvas>
|
||||
CreateFromCloneData(OffscreenCanvasCloneData* aData);
|
||||
CreateFromCloneData(nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData);
|
||||
|
||||
static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
|
||||
|
||||
@ -147,6 +168,16 @@ public:
|
||||
return mNeutered;
|
||||
}
|
||||
|
||||
void SetWriteOnly()
|
||||
{
|
||||
mIsWriteOnly = true;
|
||||
}
|
||||
|
||||
bool IsWriteOnly() const
|
||||
{
|
||||
return mIsWriteOnly;
|
||||
}
|
||||
|
||||
layers::LayersBackend GetCompositorBackendType() const
|
||||
{
|
||||
return mCompositorBackendType;
|
||||
@ -155,6 +186,8 @@ public:
|
||||
private:
|
||||
~OffscreenCanvas();
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> GetGlobalObject();
|
||||
|
||||
void CanvasAttrChanged()
|
||||
{
|
||||
mAttrDirty = true;
|
||||
@ -164,6 +197,7 @@ private:
|
||||
|
||||
bool mAttrDirty;
|
||||
bool mNeutered;
|
||||
bool mIsWriteOnly;
|
||||
|
||||
uint32_t mWidth;
|
||||
uint32_t mHeight;
|
||||
|
@ -5,10 +5,10 @@
|
||||
|
||||
#include "WebGL2Context.h"
|
||||
|
||||
#include "gfxPrefs.h"
|
||||
#include "GLContext.h"
|
||||
#include "mozilla/dom/WebGL2RenderingContextBinding.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "WebGLBuffer.h"
|
||||
#include "WebGLFormats.h"
|
||||
@ -37,7 +37,7 @@ WebGL2Context::CreateFormatUsage(gl::GLContext* gl) const
|
||||
/*static*/ bool
|
||||
WebGL2Context::IsSupported()
|
||||
{
|
||||
return Preferences::GetBool("webgl.enable-prototype-webgl2", false);
|
||||
return gfxPrefs::WebGL2Enabled();
|
||||
}
|
||||
|
||||
/*static*/ WebGL2Context*
|
||||
|
@ -1131,9 +1131,9 @@ private:
|
||||
RefPtr<HTMLCanvasElement> mCanvas;
|
||||
};
|
||||
|
||||
already_AddRefed<layers::CanvasLayer>
|
||||
already_AddRefed<layers::Layer>
|
||||
WebGLContext::GetCanvasLayer(nsDisplayListBuilder* builder,
|
||||
CanvasLayer* oldLayer,
|
||||
Layer* oldLayer,
|
||||
LayerManager* manager)
|
||||
{
|
||||
if (IsContextLost())
|
||||
@ -1141,7 +1141,7 @@ WebGLContext::GetCanvasLayer(nsDisplayListBuilder* builder,
|
||||
|
||||
if (!mResetLayer && oldLayer &&
|
||||
oldLayer->HasUserData(&gWebGLLayerUserData)) {
|
||||
RefPtr<layers::CanvasLayer> ret = oldLayer;
|
||||
RefPtr<layers::Layer> ret = oldLayer;
|
||||
return ret.forget();
|
||||
}
|
||||
|
||||
|
@ -331,8 +331,8 @@ public:
|
||||
return ActiveBoundTextureForTarget(texTarget);
|
||||
}
|
||||
|
||||
already_AddRefed<CanvasLayer>
|
||||
GetCanvasLayer(nsDisplayListBuilder* builder, CanvasLayer* oldLayer,
|
||||
already_AddRefed<Layer>
|
||||
GetCanvasLayer(nsDisplayListBuilder* builder, Layer* oldLayer,
|
||||
LayerManager* manager) override;
|
||||
|
||||
// Note that 'clean' here refers to its invalidation state, not the
|
||||
|
@ -542,9 +542,12 @@ FormatUsageAuthority::CreateForWebGL1(gl::GLContext* gl)
|
||||
fnSet(EffectiveFormat::RGBA8 , true, true);
|
||||
fnSet(EffectiveFormat::RGBA4 , true, true);
|
||||
fnSet(EffectiveFormat::RGB5_A1, true, true);
|
||||
fnSet(EffectiveFormat::RGB8 , false, true);
|
||||
fnSet(EffectiveFormat::RGB565 , true, true);
|
||||
|
||||
// RGB8 is not guaranteed to be renderable, but we should allow it for web-compat.
|
||||
// Min-capability mode should mark this as non-renderable.
|
||||
fnSet(EffectiveFormat::RGB8, true, true);
|
||||
|
||||
fnSet(EffectiveFormat::Luminance8Alpha8, false, true);
|
||||
fnSet(EffectiveFormat::Luminance8 , false, true);
|
||||
fnSet(EffectiveFormat::Alpha8 , false, true);
|
||||
|
@ -7,7 +7,7 @@
|
||||
TEST_DIRS += ['compiledtest']
|
||||
|
||||
# Number changes to this file to avoid bug 1081323 (clobber after changing a manifest):
|
||||
# 1
|
||||
# 2
|
||||
|
||||
MOCHITEST_MANIFESTS += [
|
||||
'test/crossorigin/mochitest.ini',
|
||||
@ -34,6 +34,7 @@ EXPORTS.mozilla.dom += [
|
||||
'CanvasRenderingContextHelper.h',
|
||||
'CanvasUtils.h',
|
||||
'ImageBitmap.h',
|
||||
'ImageBitmapRenderingContext.h',
|
||||
'ImageBitmapSource.h',
|
||||
'ImageData.h',
|
||||
'OffscreenCanvas.h',
|
||||
@ -50,6 +51,7 @@ UNIFIED_SOURCES += [
|
||||
'DocumentRendererChild.cpp',
|
||||
'DocumentRendererParent.cpp',
|
||||
'ImageBitmap.cpp',
|
||||
'ImageBitmapRenderingContext.cpp',
|
||||
'ImageData.cpp',
|
||||
'OffscreenCanvas.cpp',
|
||||
]
|
||||
|
@ -26,6 +26,7 @@ class nsDisplayListBuilder;
|
||||
namespace mozilla {
|
||||
namespace layers {
|
||||
class CanvasLayer;
|
||||
class Layer;
|
||||
class LayerManager;
|
||||
} // namespace layers
|
||||
namespace gfx {
|
||||
@ -39,6 +40,7 @@ class nsICanvasRenderingContextInternal :
|
||||
{
|
||||
public:
|
||||
typedef mozilla::layers::CanvasLayer CanvasLayer;
|
||||
typedef mozilla::layers::Layer Layer;
|
||||
typedef mozilla::layers::LayerManager LayerManager;
|
||||
|
||||
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICANVASRENDERINGCONTEXTINTERNAL_IID)
|
||||
@ -129,8 +131,8 @@ public:
|
||||
|
||||
// Return the CanvasLayer for this context, creating
|
||||
// one for the given layer manager if not available.
|
||||
virtual already_AddRefed<CanvasLayer> GetCanvasLayer(nsDisplayListBuilder* builder,
|
||||
CanvasLayer *oldLayer,
|
||||
virtual already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* builder,
|
||||
Layer *oldLayer,
|
||||
LayerManager *manager) = 0;
|
||||
|
||||
// Return true if the canvas should be forced to be "inactive" to ensure
|
||||
|
@ -218,6 +218,7 @@ skip-if = toolkit != 'cocoa'
|
||||
[test_2d.path.rect.zero.6.html]
|
||||
disabled = bug 407107
|
||||
[test_2d.strokeRect.zero.5.html]
|
||||
[test_bitmaprenderer.html]
|
||||
[test_bug232227.html]
|
||||
[test_bug613794.html]
|
||||
[test_bug753758.html]
|
||||
@ -246,11 +247,13 @@ support-files = captureStream_common.js
|
||||
support-files = file_drawWindow_source.html file_drawWindow_common.js
|
||||
skip-if = (buildapp == 'b2g' && toolkit != 'gonk')
|
||||
[test_imagebitmap.html]
|
||||
[test_imagebitmap_close.html]
|
||||
[test_imagebitmap_cropping.html]
|
||||
[test_imagebitmap_on_worker.html]
|
||||
[test_imagebitmap_structuredclone.html]
|
||||
[test_imagebitmap_structuredclone_iframe.html]
|
||||
[test_imagebitmap_structuredclone_window.html]
|
||||
[test_imagebitmap_transfer.html]
|
||||
[test_ImageData_ctor.html]
|
||||
[test_isPointInStroke.html]
|
||||
[test_mozDashOffset.html]
|
||||
@ -267,6 +270,10 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1040965
|
||||
[test_createPattern_broken.html]
|
||||
[test_setlinedash.html]
|
||||
[test_filter.html]
|
||||
[test_offscreencanvas_toblob.html]
|
||||
tags = offscreencanvas
|
||||
[test_offscreencanvas_toimagebitmap.html]
|
||||
tags = offscreencanvas
|
||||
[test_offscreencanvas_basic_webgl.html]
|
||||
tags = offscreencanvas
|
||||
[test_offscreencanvas_dynamic_fallback.html]
|
||||
|
@ -1,27 +1,49 @@
|
||||
/* WebWorker for test_offscreencanvas_*.html */
|
||||
(function(){
|
||||
|
||||
var port = null;
|
||||
|
||||
function ok(expect, msg) {
|
||||
if (port) {
|
||||
port.postMessage({type: "test", result: !!expect, name: msg});
|
||||
} else {
|
||||
postMessage({type: "test", result: !!expect, name: msg});
|
||||
function isInWorker() {
|
||||
try {
|
||||
return !(self instanceof Window);
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function postMessageGeneral(data) {
|
||||
if (isInWorker()) {
|
||||
if (port) {
|
||||
port.postMessage(data);
|
||||
} else {
|
||||
postMessage(data);
|
||||
}
|
||||
} else {
|
||||
postMessage(data, "*");
|
||||
}
|
||||
}
|
||||
|
||||
function ok(expect, msg) {
|
||||
postMessageGeneral({type: "test", result: !!expect, name: msg});
|
||||
}
|
||||
|
||||
function finish() {
|
||||
if (port) {
|
||||
port.postMessage({type: "finish"});
|
||||
} else {
|
||||
postMessage({type: "finish"});
|
||||
}
|
||||
postMessageGeneral({type: "finish"});
|
||||
}
|
||||
|
||||
function drawCount(count) {
|
||||
postMessageGeneral({type: "draw", count: count});
|
||||
}
|
||||
|
||||
function sendBlob(blob) {
|
||||
postMessageGeneral({type: "blob", blob: blob});
|
||||
}
|
||||
|
||||
function sendImageBitmap(img) {
|
||||
if (port) {
|
||||
port.postMessage({type: "draw", count: count});
|
||||
port.postMessage({type: "imagebitmap", bitmap: img});
|
||||
} else {
|
||||
postMessage({type: "draw", count: count});
|
||||
postMessage({type: "imagebitmap", bitmap: img});
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,7 +162,7 @@ function createDrawFunc(canvas) {
|
||||
// Start drawing
|
||||
checkGLError('after setup');
|
||||
|
||||
return function(prefix) {
|
||||
return function(prefix, needCommitFrame) {
|
||||
if (prefix) {
|
||||
prefix = "[" + prefix + "] ";
|
||||
} else {
|
||||
@ -152,7 +174,9 @@ function createDrawFunc(canvas) {
|
||||
preDraw(prefix);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
postDraw(prefix);
|
||||
if (needCommitFrame) {
|
||||
gl.commit();
|
||||
}
|
||||
checkGLError(prefix);
|
||||
};
|
||||
}
|
||||
@ -161,6 +185,9 @@ function createDrawFunc(canvas) {
|
||||
function entryFunction(testStr, subtests, offscreenCanvas) {
|
||||
var test = testStr;
|
||||
var canvas = offscreenCanvas;
|
||||
if (test == "webgl_imagebitmap") {
|
||||
canvas = new OffscreenCanvas(64, 64);
|
||||
}
|
||||
|
||||
if (test != "subworker") {
|
||||
ok(canvas, "Canvas successfully transfered to worker");
|
||||
@ -190,7 +217,7 @@ function entryFunction(testStr, subtests, offscreenCanvas) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
draw("loop " +count);
|
||||
draw("loop " +count, true);
|
||||
}, 0);
|
||||
}
|
||||
//------------------------------------------------------------------------
|
||||
@ -205,11 +232,38 @@ function entryFunction(testStr, subtests, offscreenCanvas) {
|
||||
var count = 0;
|
||||
var iid = setInterval(function() {
|
||||
++count;
|
||||
draw("loop " + count);
|
||||
draw("loop " + count, true);
|
||||
drawCount(count);
|
||||
}, 0);
|
||||
}
|
||||
//------------------------------------------------------------------------
|
||||
// Test toBlob
|
||||
//------------------------------------------------------------------------
|
||||
else if (test == "webgl_toblob") {
|
||||
draw = createDrawFunc(canvas);
|
||||
if (!draw) {
|
||||
return;
|
||||
}
|
||||
|
||||
draw("", false);
|
||||
canvas.toBlob().then(function(blob) {
|
||||
sendBlob(blob);
|
||||
});
|
||||
}
|
||||
//------------------------------------------------------------------------
|
||||
// Test toImageBitmap
|
||||
//------------------------------------------------------------------------
|
||||
else if (test == "webgl_imagebitmap") {
|
||||
draw = createDrawFunc(canvas);
|
||||
if (!draw) {
|
||||
return;
|
||||
}
|
||||
|
||||
draw("", false);
|
||||
var imgBitmap = canvas.transferToImageBitmap();
|
||||
sendImageBitmap(imgBitmap);
|
||||
}
|
||||
//------------------------------------------------------------------------
|
||||
// Canvas Size Change from Worker
|
||||
//------------------------------------------------------------------------
|
||||
else if (test == "webgl_changesize") {
|
||||
@ -219,22 +273,22 @@ function entryFunction(testStr, subtests, offscreenCanvas) {
|
||||
return;
|
||||
}
|
||||
|
||||
draw("64x64");
|
||||
draw("64x64", true);
|
||||
|
||||
setTimeout(function() {
|
||||
canvas.width = 128;
|
||||
canvas.height = 128;
|
||||
draw("Increased to 128x128");
|
||||
draw("Increased to 128x128", true);
|
||||
|
||||
setTimeout(function() {
|
||||
canvas.width = 32;
|
||||
canvas.width = 32;
|
||||
draw("Decreased to 32x32");
|
||||
draw("Decreased to 32x32", true);
|
||||
|
||||
setTimeout(function() {
|
||||
canvas.width = 64;
|
||||
canvas.height = 64;
|
||||
draw("Increased to 64x64");
|
||||
draw("Increased to 64x64", true);
|
||||
|
||||
ok(true, "Worker is done");
|
||||
finish();
|
||||
@ -297,3 +351,9 @@ onconnect = function(evt) {
|
||||
|
||||
port.start();
|
||||
};
|
||||
|
||||
if (!isInWorker()) {
|
||||
window.entryFunction = entryFunction;
|
||||
}
|
||||
|
||||
})();
|
||||
|
163
dom/canvas/test/test_bitmaprenderer.html
Normal file
163
dom/canvas/test/test_bitmaprenderer.html
Normal file
@ -0,0 +1,163 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="/tests/SimpleTest/WindowSnapshot.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/js-worker">
|
||||
function ok(expect, msg) {
|
||||
postMessage({"type": "status", status: !!expect, msg: msg});
|
||||
}
|
||||
|
||||
onmessage = function(event) {
|
||||
var bitmap = event.data.bitmap;
|
||||
ok(!!bitmap, "Get the ImageBitmap from the main script.");
|
||||
|
||||
var offscreenCanvas = new OffscreenCanvas(64, 64);
|
||||
var ctx = offscreenCanvas.getContext('bitmaprenderer');
|
||||
ok(!!ctx, "Get bitmaprenderer context on worker.");
|
||||
|
||||
ctx.transferImageBitmap(bitmap);
|
||||
var resultBitmap = offscreenCanvas.transferToImageBitmap();
|
||||
postMessage({"type": "bitmap", bitmap: resultBitmap}, [resultBitmap]);
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function createCanvas(width, height) {
|
||||
var htmlCanvas = document.createElement('canvas');
|
||||
htmlCanvas.width = width;
|
||||
htmlCanvas.height = height;
|
||||
document.body.appendChild(htmlCanvas);
|
||||
return htmlCanvas;
|
||||
}
|
||||
|
||||
function runTest(canvasWidth, canvasHeight, nextTest) {
|
||||
var canvas1 = createCanvas(canvasWidth, canvasHeight);
|
||||
var ctx = canvas1.getContext("2d");
|
||||
ctx.fillStyle = "#00FF00";
|
||||
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
var canvasRef = createCanvas(90, 90);
|
||||
var ctx = canvasRef.getContext("2d");
|
||||
// Clear with black transparent first
|
||||
ctx.fillStyle = "rgba(0, 0, 0, 0)";
|
||||
ctx.fillRect(0, 0, 90, 90);
|
||||
|
||||
ctx.fillStyle = "#00FF00";
|
||||
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
createImageBitmap(canvas1).then(function(bmp) {
|
||||
document.body.removeChild(canvas1);
|
||||
|
||||
var canvas2 = createCanvas(90, 90);
|
||||
var ctx2 = canvas2.getContext("bitmaprenderer");
|
||||
ctx2.transferImageBitmap(bmp);
|
||||
|
||||
ok(canvasRef.toDataURL() == canvas2.toDataURL(), "toDataURL should return same result.");
|
||||
|
||||
// Exam render result
|
||||
canvasRef.style.display = "none";
|
||||
canvas2.style.display = "block";
|
||||
var snapshot = snapshotWindow(window);
|
||||
|
||||
canvasRef.style.display = "block";
|
||||
canvas2.style.display = "none";
|
||||
var snapshotRef = snapshotWindow(window);
|
||||
|
||||
var results = compareSnapshots(snapshot, snapshotRef, true);
|
||||
ok(results[0], "Screenshots should be the same");
|
||||
|
||||
document.body.removeChild(canvasRef);
|
||||
document.body.removeChild(canvas2);
|
||||
|
||||
nextTest();
|
||||
});
|
||||
}
|
||||
|
||||
function scaleTest() {
|
||||
var canvas1 = createCanvas(64, 64);
|
||||
var ctx = canvas1.getContext("2d");
|
||||
ctx.fillStyle = "#00FF00";
|
||||
ctx.fillRect(0, 0, 64, 64);
|
||||
|
||||
var canvas2 = createCanvas(64, 64);
|
||||
var ctx2 = canvas2.getContext("2d");
|
||||
ctx2.fillStyle = "#00FF00";
|
||||
ctx2.fillRect(0, 0, 64, 64);
|
||||
|
||||
var p1 = createImageBitmap(canvas1);
|
||||
var p2 = createImageBitmap(canvas2);
|
||||
Promise.all([p1, p2]).then(function(bitmaps) {
|
||||
document.body.removeChild(canvas1);
|
||||
document.body.removeChild(canvas2);
|
||||
|
||||
// Create a large canvas then shrink.
|
||||
var canvas3 = createCanvas(128, 128);
|
||||
var ctx3 = canvas3.getContext("bitmaprenderer");
|
||||
ctx3.transferImageBitmap(bitmaps[0]);
|
||||
var snapshotLargeRef = snapshotWindow(window);
|
||||
|
||||
canvas3.width = 32;
|
||||
canvas3.height = 32;
|
||||
var snapshotSmall = snapshotWindow(window);
|
||||
document.body.removeChild(canvas3);
|
||||
|
||||
// Create a small canvas then grow.
|
||||
var canvas4 = createCanvas(32, 32);
|
||||
var ctx4 = canvas4.getContext("bitmaprenderer");
|
||||
ctx4.transferImageBitmap(bitmaps[1]);
|
||||
var snapshotSmallRef = snapshotWindow(window);
|
||||
|
||||
canvas4.width = 128;
|
||||
canvas4.height = 128;
|
||||
var snapshotLarge = snapshotWindow(window);
|
||||
document.body.removeChild(canvas4);
|
||||
|
||||
var resultsLarge = compareSnapshots(snapshotLarge, snapshotLargeRef, true);
|
||||
ok(resultsLarge[0], "Screenshots should be the same");
|
||||
|
||||
var resultsSmall = compareSnapshots(snapshotSmall, snapshotSmallRef, true);
|
||||
ok(resultsSmall[0], "Screenshots should be the same");
|
||||
runTestOnWorker();
|
||||
});
|
||||
}
|
||||
|
||||
function runTestOnWorker() {
|
||||
var canvas1 = createCanvas(64, 64);
|
||||
var ctx = canvas1.getContext("2d");
|
||||
ctx.fillStyle = "#00FF00";
|
||||
ctx.fillRect(0, 0, 64, 64);
|
||||
|
||||
var blob = new Blob(Array.prototype.map.call(document.querySelectorAll("script[type=\"text\/js-worker\"]"), function (oScript) { return oScript.textContent; }),{type: "text/javascript"});
|
||||
|
||||
var worker = new Worker(window.URL.createObjectURL(blob));
|
||||
|
||||
createImageBitmap(canvas1).then(function(bmp) {
|
||||
worker.postMessage({bitmap: bmp}, [bmp]);
|
||||
worker.onmessage = function(event) {
|
||||
if (event.data.type == "status") {
|
||||
ok(event.data.status, event.data.msg);
|
||||
} else if (event.data.type == "bitmap") {
|
||||
var canvas2 = createCanvas(64, 64);
|
||||
var ctx2 = canvas2.getContext('bitmaprenderer');
|
||||
ctx2.transferImageBitmap(event.data.bitmap);
|
||||
ok(canvas1.toDataURL() == canvas2.toDataURL(), 'toDataURL should be the same');
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
]}, runTest.bind(this, 64, 64, runTest.bind(this, 128, 128, scaleTest)));
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
93
dom/canvas/test/test_imagebitmap_close.html
Normal file
93
dom/canvas/test/test_imagebitmap_close.html
Normal file
@ -0,0 +1,93 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="/tests/SimpleTest/WindowSnapshot.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/js-worker">
|
||||
function ok(expect, msg) {
|
||||
postMessage({"type": "status", status: !!expect, msg: msg});
|
||||
}
|
||||
|
||||
onmessage = function(event) {
|
||||
var bitmap = event.data.bitmap;
|
||||
ok(!!bitmap, "Get the ImageBitmap from the main script.");
|
||||
bitmap.close();
|
||||
ok(bitmap.width == 0 && bitmap.height == 0, "After close(), width and height should return 0");
|
||||
postMessage({"type": "finish"});
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function createCanvas() {
|
||||
var htmlCanvas = document.createElement('canvas');
|
||||
htmlCanvas.width = 64;
|
||||
htmlCanvas.height = 64;
|
||||
document.body.appendChild(htmlCanvas);
|
||||
return htmlCanvas;
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
var canvas1 = createCanvas();
|
||||
var ctx = canvas1.getContext("2d");
|
||||
ctx.fillStyle = "#00FF00";
|
||||
ctx.fillRect(0, 0, 64, 64);
|
||||
|
||||
var canvasRef = createCanvas();
|
||||
var ctx = canvasRef.getContext("2d");
|
||||
ctx.fillStyle = "#00FF00";
|
||||
ctx.fillRect(0, 0, 64, 64);
|
||||
|
||||
createImageBitmap(canvas1).then(function(bmp) {
|
||||
var canvas2 = createCanvas();
|
||||
var ctx2 = canvas2.getContext("2d");
|
||||
ctx2.drawImage(bmp, 0, 0);
|
||||
|
||||
ok(canvasRef.toDataURL() == canvas2.toDataURL(), "toDataURL should return same result.");
|
||||
document.body.removeChild(canvas2);
|
||||
|
||||
bmp.close();
|
||||
ok(bmp.width == 0 && bmp.height == 0, "After close(), width and height should return 0");
|
||||
var canvas2 = createCanvas();
|
||||
var ctx2 = canvas2.getContext("2d");
|
||||
var beforeDrawImageDataURL = canvas2.toDataURL();
|
||||
ctx2.drawImage(bmp, 0, 0);
|
||||
var afterDrawImageDataURL = canvas2.toDataURL();
|
||||
ok(beforeDrawImageDataURL == afterDrawImageDataURL,
|
||||
"Drawing operations with a closed ImageBitmap should do nothing.");
|
||||
runTestOnWorker();
|
||||
});
|
||||
}
|
||||
|
||||
function runTestOnWorker() {
|
||||
var canvas1 = createCanvas();
|
||||
var ctx = canvas1.getContext("2d");
|
||||
ctx.fillStyle = "#00FF00";
|
||||
ctx.fillRect(0, 0, 64, 64);
|
||||
|
||||
var blob = new Blob(Array.prototype.map.call(document.querySelectorAll("script[type=\"text\/js-worker\"]"), function (oScript) { return oScript.textContent; }),{type: "text/javascript"});
|
||||
|
||||
var worker = new Worker(window.URL.createObjectURL(blob));
|
||||
|
||||
createImageBitmap(canvas1).then(function(bmp) {
|
||||
worker.postMessage({bitmap: bmp}, [bmp]);
|
||||
worker.onmessage = function(event) {
|
||||
if (event.data.type == "status") {
|
||||
ok(event.data.status, event.data.msg);
|
||||
} else if (event.data.type == "finish") {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runTest();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
111
dom/canvas/test/test_imagebitmap_transfer.html
Normal file
111
dom/canvas/test/test_imagebitmap_transfer.html
Normal file
@ -0,0 +1,111 @@
|
||||
<!DOCTYPE HTML>
|
||||
<title>Test ImageBitmap : Transfer</title>
|
||||
<meta charset="utf-8">
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
|
||||
var gImage1;
|
||||
var gImage2;
|
||||
var gImageBitmap1;
|
||||
var gImageBitmap2;
|
||||
|
||||
function isPixel(ctx1, ctx2, x, y) {
|
||||
var pixel1 = ctx1.getImageData(x, y, 1, 1);
|
||||
var pixel2 = ctx2.getImageData(x, y, 1, 1);
|
||||
ok(pixel1.data[0] == pixel2.data[0] &&
|
||||
pixel1.data[1] == pixel2.data[1] &&
|
||||
pixel1.data[2] == pixel2.data[2] &&
|
||||
pixel1.data[3] == pixel2.data[3],
|
||||
"Color(" + pixel1.data[0] + ", " + pixel1.data[1] + ", " + pixel1.data[2] + ", " + pixel1.data[3] + ") should qual to Color(" + pixel2.data[0] + ", " + pixel2.data[1] + ", " + pixel2.data[2] + ", " + pixel2.data[3] + ")");
|
||||
}
|
||||
|
||||
function compareImageBitmapWithImageElement(imageBitmap, imageElement) {
|
||||
var canvas1 = document.createElement('canvas');
|
||||
var canvas2 = document.createElement('canvas');
|
||||
|
||||
canvas1.width = imageElement.naturalWidth;
|
||||
canvas1.height = imageElement.naturalHeight;
|
||||
canvas2.width = imageElement.naturalWidth;
|
||||
canvas2.height = imageElement.naturalHeight;
|
||||
|
||||
var ctx1 = canvas1.getContext('2d');
|
||||
var ctx2 = canvas2.getContext('2d');
|
||||
|
||||
ctx1.drawImage(imageElement, 0, 0);
|
||||
ctx2.drawImage(imageBitmap, 0, 0);
|
||||
|
||||
document.body.appendChild(canvas1);
|
||||
document.body.appendChild(canvas2);
|
||||
|
||||
for (var t = 0; t < 20; ++t) {
|
||||
// check one random pixel
|
||||
var randomX = Math.floor(Math.random() * imageElement.naturalWidth);
|
||||
var randomY = Math.floor(Math.random() * imageElement.naturalHeight);
|
||||
isPixel(ctx1, ctx2, randomX, randomY);
|
||||
}
|
||||
}
|
||||
|
||||
var worker = new Worker("imagebitmap_structuredclone.js");
|
||||
worker.onmessage = function(event) {
|
||||
|
||||
if (event.data.type == "status") {
|
||||
ok(event.data.status, event.data.msg);
|
||||
} else if (event.data.type == "finish") {
|
||||
SimpleTest.finish();
|
||||
} else if (event.data.type == "bitmap1") {
|
||||
compareImageBitmapWithImageElement(event.data.bitmap, gImage1);
|
||||
} else if (event.data.type == "bitmap2") {
|
||||
compareImageBitmapWithImageElement(event.data.bitmap, gImage2);
|
||||
}
|
||||
}
|
||||
|
||||
function prepareTwoImageBitmap() {
|
||||
gImage1 = document.createElement('img');
|
||||
gImage2 = document.createElement('img');
|
||||
gImage1.src = "image_rgrg-256x256.png";
|
||||
gImage2.src = "image_yellow.png";
|
||||
|
||||
var p1 = new Promise(function(resolve, reject) {
|
||||
gImage1.onload = function() {
|
||||
var promise = createImageBitmap(gImage1);
|
||||
promise.then(function(bitmap) {
|
||||
gImageBitmap1 = bitmap;
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var p2 = new Promise(function(resolve, reject) {
|
||||
gImage2.onload = function() {
|
||||
var promise = createImageBitmap(gImage2);
|
||||
promise.then(function(bitmap) {
|
||||
gImageBitmap2 = bitmap;
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all([p1, p2]);
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
ok(worker, "Worker created successfully.");
|
||||
|
||||
prepareTwoImageBitmap().then(function(){
|
||||
worker.postMessage({"bitmap1":gImageBitmap1, "bitmap2":gImageBitmap2},
|
||||
[gImageBitmap1, gImageBitmap2]);
|
||||
|
||||
ok(gImageBitmap1.width == 0 && gImageBitmap1.height == 0,
|
||||
"After transfer, ImageBitmap become neutered");
|
||||
ok(gImageBitmap2.width == 0 && gImageBitmap2.height == 0,
|
||||
"After transfer, ImageBitmap become neutered");
|
||||
});
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(runTests);
|
||||
|
||||
</script>
|
||||
</body>
|
91
dom/canvas/test/test_offscreencanvas_toblob.html
Normal file
91
dom/canvas/test/test_offscreencanvas_toblob.html
Normal file
@ -0,0 +1,91 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="offscreencanvas.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c" width="64" height="64"></canvas>
|
||||
<canvas id="c-mt" width="64" height="64"></canvas>
|
||||
<canvas id="c-ref" width="64" height="64"></canvas>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function testBlob(blob, callback) {
|
||||
// testing toBlob
|
||||
// Fill c-ref with green color.
|
||||
var c = document.getElementById("c-ref");
|
||||
var ctx = c.getContext("2d");
|
||||
ctx.rect(0, 0, 64, 64);
|
||||
ctx.fillStyle = "#00FF00";
|
||||
ctx.fill();
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
ok(c.toDataURL() == e.target.result, "toBlob should return a 64x64 green square");
|
||||
callback();
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
|
||||
function runTestOnMainThread() {
|
||||
var htmlCanvas = document.getElementById("c-mt");
|
||||
ok(htmlCanvas, "Should have HTML canvas element");
|
||||
|
||||
window.onmessage = function(evt) {
|
||||
var msg = evt.data || {};
|
||||
if (msg.type == "test") {
|
||||
ok(msg.result, msg.name);
|
||||
}
|
||||
if (msg.type == "blob") {
|
||||
testBlob(msg.blob, SimpleTest.finish);
|
||||
}
|
||||
}
|
||||
|
||||
ok(htmlCanvas.transferControlToOffscreen, "HTMLCanvasElement has transferControlToOffscreen function");
|
||||
|
||||
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
|
||||
ok(offscreenCanvas, "Expected transferControlToOffscreen to succeed");
|
||||
|
||||
entryFunction('webgl_toblob', '', offscreenCanvas);
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
|
||||
var htmlCanvas = document.getElementById("c");
|
||||
var worker = new Worker("offscreencanvas.js");
|
||||
|
||||
ok(htmlCanvas, "Should have HTML canvas element");
|
||||
ok(worker, "Web worker successfully created");
|
||||
|
||||
worker.onmessage = function(evt) {
|
||||
var msg = evt.data || {};
|
||||
if (msg.type == "test") {
|
||||
ok(msg.result, msg.name);
|
||||
}
|
||||
if (msg.type == "blob") {
|
||||
testBlob(msg.blob, function() {
|
||||
worker.terminate();
|
||||
runTestOnMainThread();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ok(htmlCanvas.transferControlToOffscreen, "HTMLCanvasElement has transferControlToOffscreen function");
|
||||
|
||||
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
|
||||
ok(offscreenCanvas, "Expected transferControlToOffscreen to succeed");
|
||||
|
||||
worker.postMessage({test: 'webgl_toblob', canvas: offscreenCanvas}, [offscreenCanvas]);
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
69
dom/canvas/test/test_offscreencanvas_toimagebitmap.html
Normal file
69
dom/canvas/test/test_offscreencanvas_toimagebitmap.html
Normal file
@ -0,0 +1,69 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c" width="64" height="64"></canvas>
|
||||
<canvas id="c2" width="64" height="64"></canvas>
|
||||
<canvas id="c-ref" width="64" height="64"></canvas>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function runTest() {
|
||||
|
||||
var worker = new Worker("offscreencanvas.js");
|
||||
|
||||
ok(worker, "Web worker successfully created");
|
||||
|
||||
worker.onmessage = function(evt) {
|
||||
var msg = evt.data || {};
|
||||
if (msg.type == "test") {
|
||||
ok(msg.result, msg.name);
|
||||
}
|
||||
if (msg.type == "imagebitmap") {
|
||||
// testing toBlob
|
||||
// Fill c-ref with green color.
|
||||
var c = document.getElementById("c-ref");
|
||||
var ctx = c.getContext("2d");
|
||||
ctx.rect(0, 0, 64, 64);
|
||||
ctx.fillStyle = "#00FF00";
|
||||
ctx.fill();
|
||||
|
||||
var htmlCanvas = document.getElementById("c");
|
||||
var bitmapRenderer = htmlCanvas.getContext("bitmaprenderer");
|
||||
bitmapRenderer.transferImageBitmap(msg.bitmap);
|
||||
|
||||
ok(c.toDataURL() == htmlCanvas.toDataURL(),
|
||||
"imagebitmap should return a 64x64 green square");
|
||||
|
||||
// The ownership of msg.bitmap should be transferred to canvas "c" when
|
||||
// we called transferImageBitmap. So we test if the ownership is actually
|
||||
// transferred here.
|
||||
var htmlCanvas = document.getElementById("c2");
|
||||
var bitmapRenderer = htmlCanvas.getContext("bitmaprenderer");
|
||||
bitmapRenderer.transferImageBitmap(msg.bitmap);
|
||||
|
||||
SimpleTest.doesThrow(
|
||||
function() { c2.toDataURL(); },
|
||||
"ImageBitmap has been transferred, toDataURL will throw.");
|
||||
|
||||
worker.terminate();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
worker.postMessage({test: 'webgl_imagebitmap'});
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -13,7 +13,6 @@ fail-if = (os == 'b2g')
|
||||
support-files = captureStream_common.js
|
||||
[webgl-mochitest/test_cubemap_must_be_square.html]
|
||||
[webgl-mochitest/test_depth_tex_lazy_clear.html]
|
||||
skip-if = 1
|
||||
[webgl-mochitest/test_draw.html]
|
||||
[webgl-mochitest/test_fb_param.html]
|
||||
[webgl-mochitest/test_fb_param_crash.html]
|
||||
|
@ -374,7 +374,8 @@ HTMLCanvasElement::~HTMLCanvasElement()
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLCanvasElement, nsGenericHTMLElement,
|
||||
mCurrentContext, mPrintCallback,
|
||||
mPrintState, mOriginalCanvas)
|
||||
mPrintState, mOriginalCanvas,
|
||||
mOffscreenCanvas)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(HTMLCanvasElement, Element)
|
||||
NS_IMPL_RELEASE_INHERITED(HTMLCanvasElement, Element)
|
||||
@ -779,10 +780,17 @@ HTMLCanvasElement::TransferControlToOffscreen(ErrorResult& aRv)
|
||||
renderer->SetWidth(sz.width);
|
||||
renderer->SetHeight(sz.height);
|
||||
|
||||
mOffscreenCanvas = new OffscreenCanvas(sz.width,
|
||||
nsCOMPtr<nsIGlobalObject> global =
|
||||
do_QueryInterface(OwnerDoc()->GetInnerWindow());
|
||||
mOffscreenCanvas = new OffscreenCanvas(global,
|
||||
sz.width,
|
||||
sz.height,
|
||||
GetCompositorBackendType(),
|
||||
renderer);
|
||||
if (mWriteOnly) {
|
||||
mOffscreenCanvas->SetWriteOnly();
|
||||
}
|
||||
|
||||
if (!mContextObserver) {
|
||||
mContextObserver = new HTMLCanvasElementObserver(this);
|
||||
}
|
||||
@ -1038,9 +1046,9 @@ HTMLCanvasElement::GetOpaqueAttr()
|
||||
return HasAttr(kNameSpaceID_None, nsGkAtoms::moz_opaque);
|
||||
}
|
||||
|
||||
already_AddRefed<CanvasLayer>
|
||||
already_AddRefed<Layer>
|
||||
HTMLCanvasElement::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
|
||||
CanvasLayer *aOldLayer,
|
||||
Layer *aOldLayer,
|
||||
LayerManager *aManager)
|
||||
{
|
||||
// The address of sOffscreenCanvasLayerUserDataDummy is used as the user
|
||||
@ -1056,7 +1064,7 @@ HTMLCanvasElement::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
|
||||
if (mOffscreenCanvas) {
|
||||
if (!mResetLayer &&
|
||||
aOldLayer && aOldLayer->HasUserData(&sOffscreenCanvasLayerUserDataDummy)) {
|
||||
RefPtr<CanvasLayer> ret = aOldLayer;
|
||||
RefPtr<Layer> ret = aOldLayer;
|
||||
return ret.forget();
|
||||
}
|
||||
|
||||
@ -1173,7 +1181,7 @@ void
|
||||
HTMLCanvasElement::SetFrameCapture(already_AddRefed<SourceSurface> aSurface)
|
||||
{
|
||||
RefPtr<SourceSurface> surface = aSurface;
|
||||
RefPtr<CairoImage> image = new CairoImage(surface->GetSize(), surface);
|
||||
RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surface->GetSize(), surface);
|
||||
|
||||
// Loop backwards to allow removing elements in the loop.
|
||||
for (int i = mRequestedFrameListeners.Length() - 1; i >= 0; --i) {
|
||||
|
@ -31,6 +31,7 @@ namespace layers {
|
||||
class AsyncCanvasRenderer;
|
||||
class CanvasLayer;
|
||||
class Image;
|
||||
class Layer;
|
||||
class LayerManager;
|
||||
} // namespace layers
|
||||
namespace gfx {
|
||||
@ -120,6 +121,7 @@ class HTMLCanvasElement final : public nsGenericHTMLElement,
|
||||
|
||||
typedef layers::AsyncCanvasRenderer AsyncCanvasRenderer;
|
||||
typedef layers::CanvasLayer CanvasLayer;
|
||||
typedef layers::Layer Layer;
|
||||
typedef layers::LayerManager LayerManager;
|
||||
|
||||
public:
|
||||
@ -308,8 +310,8 @@ public:
|
||||
* Helpers called by various users of Canvas
|
||||
*/
|
||||
|
||||
already_AddRefed<CanvasLayer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
|
||||
CanvasLayer *aOldLayer,
|
||||
already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
|
||||
Layer *aOldLayer,
|
||||
LayerManager *aManager);
|
||||
// Should return true if the canvas layer should always be marked inactive.
|
||||
// We should return true here if we can't do accelerated compositing with
|
||||
|
@ -996,7 +996,7 @@ HTMLImageElement::PictureSourceSrcsetChanged(nsIContent *aSourceNode,
|
||||
mResponsiveSelector->SetCandidatesFromSourceSet(aNewValue);
|
||||
}
|
||||
|
||||
if (!mInDocResponsiveContent) {
|
||||
if (!mInDocResponsiveContent && IsInComposedDoc()) {
|
||||
nsIDocument* doc = GetOurOwnerDoc();
|
||||
if (doc) {
|
||||
doc->AddResponsiveContent(this);
|
||||
|
@ -295,20 +295,25 @@ NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback, nsIContentPrefCallback2)
|
||||
NS_IMETHODIMP
|
||||
UploadLastDir::ContentPrefCallback::HandleCompletion(uint16_t aReason)
|
||||
{
|
||||
nsCOMPtr<nsIFile> localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
|
||||
NS_ENSURE_STATE(localFile);
|
||||
|
||||
if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR ||
|
||||
!mResult) {
|
||||
// Default to "desktop" directory for each platform
|
||||
nsCOMPtr<nsIFile> homeDir;
|
||||
NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(homeDir));
|
||||
localFile = do_QueryInterface(homeDir);
|
||||
} else {
|
||||
nsCOMPtr<nsIFile> localFile;
|
||||
nsAutoString prefStr;
|
||||
|
||||
if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR || !mResult) {
|
||||
prefStr = Preferences::GetString("dom.input.fallbackUploadDir");
|
||||
if (prefStr.IsEmpty()) {
|
||||
// If no custom directory was set through the pref, default to
|
||||
// "desktop" directory for each platform.
|
||||
NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(localFile));
|
||||
}
|
||||
}
|
||||
|
||||
if (!localFile) {
|
||||
if (prefStr.IsEmpty() && mResult) {
|
||||
nsCOMPtr<nsIVariant> pref;
|
||||
mResult->GetValue(getter_AddRefs(pref));
|
||||
pref->GetAsAString(prefStr);
|
||||
}
|
||||
localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
|
||||
localFile->InitWithPath(prefStr);
|
||||
}
|
||||
|
||||
|
@ -600,3 +600,5 @@ skip-if = buildapp == 'b2g' # bug 1129014
|
||||
[test_image_clone_load.html]
|
||||
[test_bug1203668.html]
|
||||
[test_bug1166138.html]
|
||||
[test_filepicker_default_directory.html]
|
||||
skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android'
|
||||
|
83
dom/html/test/test_filepicker_default_directory.html
Normal file
83
dom/html/test/test_filepicker_default_directory.html
Normal file
@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1194893
|
||||
-->
|
||||
<head>
|
||||
<title>Test for filepicker default directory</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1194893">Mozilla Bug 1194893</a>
|
||||
<div id="content">
|
||||
<input type="file" id="f">
|
||||
</div>
|
||||
<pre id="text">
|
||||
<script class="testbody" type="application/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
const { Cc: Cc, Ci: Ci } = SpecialPowers;
|
||||
|
||||
// Platform-independent directory names are #define'd in xpcom/io/nsDirectoryServiceDefs.h
|
||||
var defaultUploadDirectory = Cc["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Ci.nsIDirectoryService)
|
||||
.QueryInterface(Ci.nsIProperties)
|
||||
.get("Desk", Ci.nsIFile);
|
||||
|
||||
// When we want to test an upload directory other than the default, we need to
|
||||
// get a valid directory in a platform-independent way. Since NS_OS_DESKTOP_DIR
|
||||
// may fallback to NS_OS_HOME_DIR, let's use NS_OS_TMP_DIR.
|
||||
var customUploadDirectory = Cc["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Ci.nsIDirectoryService)
|
||||
.QueryInterface(Ci.nsIProperties)
|
||||
.get("TmpD", Ci.nsIFile);
|
||||
|
||||
// Useful for debugging
|
||||
//info("defaultUploadDirectory" + defaultUploadDirectory.path);
|
||||
//info("customUploadDirectory" + customUploadDirectory.path);
|
||||
|
||||
var MockFilePicker = SpecialPowers.MockFilePicker;
|
||||
MockFilePicker.init(window);
|
||||
|
||||
// need to show the MockFilePicker so .displayDirectory gets set
|
||||
var f = document.getElementById("f");
|
||||
f.focus();
|
||||
|
||||
var testIndex = 0;
|
||||
var tests = [
|
||||
["", defaultUploadDirectory.path],
|
||||
[customUploadDirectory.path, customUploadDirectory.path]
|
||||
]
|
||||
|
||||
MockFilePicker.showCallback = function(filepicker) {
|
||||
info(SpecialPowers.wrap(MockFilePicker).displayDirectory.path);
|
||||
|
||||
is(SpecialPowers.wrap(MockFilePicker).displayDirectory.path,
|
||||
tests[testIndex][1]);
|
||||
|
||||
if (++testIndex == tests.length) {
|
||||
MockFilePicker.cleanup();
|
||||
SimpleTest.finish();
|
||||
} else {
|
||||
launchNextTest();
|
||||
}
|
||||
}
|
||||
|
||||
function launchNextTest() {
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{ 'set': [
|
||||
['dom.input.fallbackUploadDir', tests[testIndex][0]],
|
||||
]},
|
||||
function () {
|
||||
f.click();
|
||||
});
|
||||
}
|
||||
|
||||
launchNextTest();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -44,7 +44,7 @@ StructuredCloneData::Copy(const StructuredCloneData& aData)
|
||||
MOZ_ASSERT(BlobImpls().IsEmpty());
|
||||
BlobImpls().AppendElements(aData.BlobImpls());
|
||||
|
||||
MOZ_ASSERT(GetImages().IsEmpty());
|
||||
MOZ_ASSERT(GetSurfaces().IsEmpty());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -790,6 +790,7 @@ MediaDecoder::Seek(double aTime, SeekTarget::Type aSeekType)
|
||||
|
||||
UpdateDormantState(false /* aDormantTimeout */, true /* aActivity */);
|
||||
|
||||
MOZ_ASSERT(!mIsDormant, "should be out of dormant by now");
|
||||
MOZ_ASSERT(aTime >= 0.0, "Cannot seek to a negative value.");
|
||||
|
||||
int64_t timeUsecs = 0;
|
||||
|
@ -782,6 +782,17 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
|
||||
self->WaitRequestRef(aRejection.mType).Complete();
|
||||
}));
|
||||
|
||||
// We are out of data to decode and will enter buffering mode soon.
|
||||
// We want to play the frames we have already decoded, so we stop pre-rolling
|
||||
// and ensure that loadeddata is fired as required.
|
||||
if (isAudio) {
|
||||
StopPrerollingAudio();
|
||||
} else {
|
||||
StopPrerollingVideo();
|
||||
}
|
||||
if (mState == DECODER_STATE_BUFFERING || mState == DECODER_STATE_DECODING) {
|
||||
MaybeFinishDecodeFirstFrame();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1481,7 +1492,7 @@ MediaDecoderStateMachine::Seek(SeekTarget aTarget)
|
||||
return MediaDecoder::SeekPromise::CreateAndReject(/* aIgnored = */ true, __func__);
|
||||
}
|
||||
|
||||
NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA,
|
||||
MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA,
|
||||
"We should have got duration already");
|
||||
|
||||
if (mState < DECODER_STATE_DECODING ||
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "mozilla/AbstractThread.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/IndexSequence.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/Tuple.h"
|
||||
#include "mozilla/TypeTraits.h"
|
||||
|
@ -546,6 +546,9 @@ MediaFormatReader::OnDemuxFailed(TrackType aTrack, DemuxerFailureReason aFailure
|
||||
NotifyError(aTrack);
|
||||
break;
|
||||
case DemuxerFailureReason::WAITING_FOR_DATA:
|
||||
if (!decoder.mWaitingForData) {
|
||||
decoder.mNeedDraining = true;
|
||||
}
|
||||
NotifyWaitingForData(aTrack);
|
||||
break;
|
||||
case DemuxerFailureReason::CANCELED:
|
||||
@ -691,6 +694,9 @@ MediaFormatReader::NotifyWaitingForData(TrackType aTrack)
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
auto& decoder = GetDecoderData(aTrack);
|
||||
decoder.mWaitingForData = true;
|
||||
if (decoder.mTimeThreshold) {
|
||||
decoder.mTimeThreshold.ref().mWaiting = true;
|
||||
}
|
||||
ScheduleUpdate(aTrack);
|
||||
}
|
||||
|
||||
@ -760,17 +766,31 @@ MediaFormatReader::UpdateReceivedNewData(TrackType aTrack)
|
||||
if (!decoder.mReceivedNewData) {
|
||||
return false;
|
||||
}
|
||||
decoder.mReceivedNewData = false;
|
||||
decoder.mWaitingForData = false;
|
||||
bool hasLastEnd;
|
||||
media::TimeUnit lastEnd = decoder.mTimeRanges.GetEnd(&hasLastEnd);
|
||||
|
||||
// Update our cached TimeRange.
|
||||
decoder.mTimeRanges = decoder.mTrackDemuxer->GetBuffered();
|
||||
if (decoder.mTimeRanges.Length() &&
|
||||
(!hasLastEnd || decoder.mTimeRanges.GetEnd() > lastEnd)) {
|
||||
|
||||
if (decoder.mDrainComplete || decoder.mDraining) {
|
||||
// We do not want to clear mWaitingForData or mDemuxEOS while
|
||||
// a drain is in progress in order to properly complete the operation.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasLastEnd;
|
||||
media::TimeUnit lastEnd = decoder.mTimeRanges.GetEnd(&hasLastEnd);
|
||||
if (hasLastEnd) {
|
||||
if (decoder.mLastTimeRangesEnd && decoder.mLastTimeRangesEnd.ref() > lastEnd) {
|
||||
// New data was added after our previous end, we can clear the EOS flag.
|
||||
decoder.mDemuxEOS = false;
|
||||
}
|
||||
decoder.mLastTimeRangesEnd = Some(lastEnd);
|
||||
}
|
||||
|
||||
decoder.mReceivedNewData = false;
|
||||
if (decoder.mTimeThreshold) {
|
||||
decoder.mTimeThreshold.ref().mWaiting = false;
|
||||
}
|
||||
decoder.mWaitingForData = false;
|
||||
|
||||
if (decoder.mError) {
|
||||
return false;
|
||||
@ -808,6 +828,8 @@ MediaFormatReader::RequestDemuxSamples(TrackType aTrack)
|
||||
|
||||
if (decoder.mDemuxEOS) {
|
||||
// Nothing left to demux.
|
||||
// We do not want to attempt to demux while in waiting for data mode
|
||||
// as it would retrigger an unecessary drain.
|
||||
return;
|
||||
}
|
||||
|
||||
@ -883,6 +905,7 @@ MediaFormatReader::HandleDemuxedSamples(TrackType aTrack,
|
||||
info->GetID());
|
||||
decoder.mInfo = info;
|
||||
decoder.mLastStreamSourceID = info->GetID();
|
||||
decoder.mNextStreamSourceID.reset();
|
||||
// Flush will clear our array of queued samples. So make a copy now.
|
||||
nsTArray<RefPtr<MediaRawData>> samples{decoder.mQueuedSamples};
|
||||
Flush(aTrack);
|
||||
@ -892,38 +915,11 @@ MediaFormatReader::HandleDemuxedSamples(TrackType aTrack,
|
||||
decoder.mQueuedSamples.AppendElements(Move(samples));
|
||||
NotifyDecodingRequested(aTrack);
|
||||
} else {
|
||||
MOZ_ASSERT(decoder.mTimeThreshold.isNothing());
|
||||
SeekTarget seekTarget =
|
||||
decoder.mTimeThreshold.refOr(SeekTarget(TimeUnit::FromMicroseconds(sample->mTime), false));
|
||||
LOG("Stream change occurred on a non-keyframe. Seeking to:%lld",
|
||||
sample->mTime);
|
||||
decoder.mTimeThreshold = Some(TimeUnit::FromMicroseconds(sample->mTime));
|
||||
RefPtr<MediaFormatReader> self = this;
|
||||
decoder.ResetDemuxer();
|
||||
decoder.mSeekRequest.Begin(decoder.mTrackDemuxer->Seek(decoder.mTimeThreshold.ref())
|
||||
->Then(OwnerThread(), __func__,
|
||||
[self, aTrack] (media::TimeUnit aTime) {
|
||||
auto& decoder = self->GetDecoderData(aTrack);
|
||||
decoder.mSeekRequest.Complete();
|
||||
self->NotifyDecodingRequested(aTrack);
|
||||
},
|
||||
[self, aTrack] (DemuxerFailureReason aResult) {
|
||||
auto& decoder = self->GetDecoderData(aTrack);
|
||||
decoder.mSeekRequest.Complete();
|
||||
switch (aResult) {
|
||||
case DemuxerFailureReason::WAITING_FOR_DATA:
|
||||
self->NotifyWaitingForData(aTrack);
|
||||
break;
|
||||
case DemuxerFailureReason::END_OF_STREAM:
|
||||
self->NotifyEndOfStream(aTrack);
|
||||
break;
|
||||
case DemuxerFailureReason::CANCELED:
|
||||
case DemuxerFailureReason::SHUTDOWN:
|
||||
break;
|
||||
default:
|
||||
self->NotifyError(aTrack);
|
||||
break;
|
||||
}
|
||||
decoder.mTimeThreshold.reset();
|
||||
}));
|
||||
seekTarget.mTime.ToMicroseconds());
|
||||
InternalSeek(aTrack, seekTarget);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -957,6 +953,42 @@ MediaFormatReader::HandleDemuxedSamples(TrackType aTrack,
|
||||
decoder.mInputExhausted = false;
|
||||
}
|
||||
|
||||
void
|
||||
MediaFormatReader::InternalSeek(TrackType aTrack, const SeekTarget& aTarget)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
auto& decoder = GetDecoderData(aTrack);
|
||||
decoder.mTimeThreshold = Some(aTarget);
|
||||
RefPtr<MediaFormatReader> self = this;
|
||||
decoder.ResetDemuxer();
|
||||
decoder.mSeekRequest.Begin(decoder.mTrackDemuxer->Seek(decoder.mTimeThreshold.ref().mTime)
|
||||
->Then(OwnerThread(), __func__,
|
||||
[self, aTrack] (media::TimeUnit aTime) {
|
||||
auto& decoder = self->GetDecoderData(aTrack);
|
||||
decoder.mSeekRequest.Complete();
|
||||
self->NotifyDecodingRequested(aTrack);
|
||||
},
|
||||
[self, aTrack] (DemuxerFailureReason aResult) {
|
||||
auto& decoder = self->GetDecoderData(aTrack);
|
||||
decoder.mSeekRequest.Complete();
|
||||
switch (aResult) {
|
||||
case DemuxerFailureReason::WAITING_FOR_DATA:
|
||||
self->NotifyWaitingForData(aTrack);
|
||||
break;
|
||||
case DemuxerFailureReason::END_OF_STREAM:
|
||||
self->NotifyEndOfStream(aTrack);
|
||||
break;
|
||||
case DemuxerFailureReason::CANCELED:
|
||||
case DemuxerFailureReason::SHUTDOWN:
|
||||
break;
|
||||
default:
|
||||
self->NotifyError(aTrack);
|
||||
break;
|
||||
}
|
||||
decoder.mTimeThreshold.reset();
|
||||
}));
|
||||
}
|
||||
|
||||
void
|
||||
MediaFormatReader::DrainDecoder(TrackType aTrack)
|
||||
{
|
||||
@ -992,7 +1024,6 @@ MediaFormatReader::Update(TrackType aTrack)
|
||||
|
||||
LOGV("Processing update for %s", TrackTypeToStr(aTrack));
|
||||
|
||||
bool needInput = false;
|
||||
bool needOutput = false;
|
||||
auto& decoder = GetDecoderData(aTrack);
|
||||
decoder.mUpdateScheduled = false;
|
||||
@ -1006,87 +1037,108 @@ MediaFormatReader::Update(TrackType aTrack)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!decoder.HasPromise() && decoder.mWaitingForData) {
|
||||
// Nothing more we can do at present.
|
||||
LOGV("Still waiting for data.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Record number of frames decoded and parsed. Automatically update the
|
||||
// stats counters using the AutoNotifyDecoded stack-based class.
|
||||
AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder);
|
||||
|
||||
if (aTrack == TrackInfo::kVideoTrack) {
|
||||
uint64_t delta =
|
||||
decoder.mNumSamplesOutputTotal - mLastReportedNumDecodedFrames;
|
||||
a.mDecoded = static_cast<uint32_t>(delta);
|
||||
mLastReportedNumDecodedFrames = decoder.mNumSamplesOutputTotal;
|
||||
// Drop any frames found prior our internal seek target.
|
||||
while (decoder.mTimeThreshold && decoder.mOutput.Length()) {
|
||||
RefPtr<MediaData>& output = decoder.mOutput[0];
|
||||
SeekTarget target = decoder.mTimeThreshold.ref();
|
||||
media::TimeUnit time = media::TimeUnit::FromMicroseconds(output->mTime);
|
||||
if (time >= target.mTime) {
|
||||
// We have reached our internal seek target.
|
||||
decoder.mTimeThreshold.reset();
|
||||
}
|
||||
if (time < target.mTime || target.mDropTarget) {
|
||||
LOGV("Internal Seeking: Dropping %s frame time:%f wanted:%f (kf:%d)",
|
||||
TrackTypeToStr(aTrack),
|
||||
media::TimeUnit::FromMicroseconds(output->mTime).ToSeconds(),
|
||||
target.mTime.ToSeconds(),
|
||||
output->mKeyframe);
|
||||
decoder.mOutput.RemoveElementAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (decoder.HasPromise()) {
|
||||
needOutput = true;
|
||||
if (!decoder.mOutput.IsEmpty()) {
|
||||
if (decoder.mOutput.Length()) {
|
||||
// We have a decoded sample ready to be returned.
|
||||
if (aTrack == TrackType::kVideoTrack) {
|
||||
uint64_t delta =
|
||||
decoder.mNumSamplesOutputTotal - mLastReportedNumDecodedFrames;
|
||||
a.mDecoded = static_cast<uint32_t>(delta);
|
||||
mLastReportedNumDecodedFrames = decoder.mNumSamplesOutputTotal;
|
||||
nsCString error;
|
||||
mVideo.mIsHardwareAccelerated =
|
||||
mVideo.mDecoder && mVideo.mDecoder->IsHardwareAccelerated(error);
|
||||
}
|
||||
while (decoder.mOutput.Length()) {
|
||||
RefPtr<MediaData> output = decoder.mOutput[0];
|
||||
decoder.mOutput.RemoveElementAt(0);
|
||||
decoder.mSizeOfQueue -= 1;
|
||||
if (decoder.mTimeThreshold.isNothing() ||
|
||||
media::TimeUnit::FromMicroseconds(output->mTime) >= decoder.mTimeThreshold.ref()) {
|
||||
decoder.mLastSampleTime =
|
||||
Some(media::TimeUnit::FromMicroseconds(output->mTime));
|
||||
ReturnOutput(output, aTrack);
|
||||
decoder.mTimeThreshold.reset();
|
||||
break;
|
||||
} else {
|
||||
LOGV("Internal Seeking: Dropping frame time:%f wanted:%f (kf:%d)",
|
||||
media::TimeUnit::FromMicroseconds(output->mTime).ToSeconds(),
|
||||
decoder.mTimeThreshold.ref().ToSeconds(),
|
||||
output->mKeyframe);
|
||||
}
|
||||
}
|
||||
} else if (decoder.mError) {
|
||||
LOG("Rejecting %s promise: DECODE_ERROR", TrackTypeToStr(aTrack));
|
||||
decoder.RejectPromise(DECODE_ERROR, __func__);
|
||||
return;
|
||||
} else if (decoder.mDrainComplete) {
|
||||
bool wasDraining = decoder.mDraining;
|
||||
decoder.mDrainComplete = false;
|
||||
decoder.mDraining = false;
|
||||
if (decoder.mError) {
|
||||
LOG("Decoding Error");
|
||||
decoder.RejectPromise(DECODE_ERROR, __func__);
|
||||
return;
|
||||
} else if (decoder.mDemuxEOS) {
|
||||
if (decoder.mDemuxEOS) {
|
||||
LOG("Rejecting %s promise: EOS", TrackTypeToStr(aTrack));
|
||||
decoder.RejectPromise(END_OF_STREAM, __func__);
|
||||
}
|
||||
} else if (decoder.mError) {
|
||||
decoder.RejectPromise(DECODE_ERROR, __func__);
|
||||
return;
|
||||
} else if (decoder.mWaitingForData) {
|
||||
LOG("Waiting For Data");
|
||||
if (wasDraining && decoder.mLastSampleTime &&
|
||||
!decoder.mNextStreamSourceID) {
|
||||
// We have completed draining the decoder following WaitingForData.
|
||||
// Set up the internal seek machinery to be able to resume from the
|
||||
// last sample decoded.
|
||||
LOG("Seeking to last sample time: %lld",
|
||||
decoder.mLastSampleTime.ref().ToMicroseconds());
|
||||
InternalSeek(aTrack, SeekTarget(decoder.mLastSampleTime.ref(), true));
|
||||
}
|
||||
LOG("Rejecting %s promise: WAITING_FOR_DATA", TrackTypeToStr(aTrack));
|
||||
decoder.RejectPromise(WAITING_FOR_DATA, __func__);
|
||||
}
|
||||
// Now that draining has completed, we check if we have received
|
||||
// new data again as the result may now be different from the earlier
|
||||
// run.
|
||||
if (UpdateReceivedNewData(aTrack)) {
|
||||
LOGV("Nothing more to do");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (decoder.mNeedDraining) {
|
||||
DrainDecoder(aTrack);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NeedInput(decoder)) {
|
||||
bool needInput = NeedInput(decoder);
|
||||
|
||||
LOGV("Update(%s) ni=%d no=%d ie=%d, in:%llu out:%llu qs=%u pending:%u waiting:%d ahead:%d sid:%u",
|
||||
TrackTypeToStr(aTrack), needInput, needOutput, decoder.mInputExhausted,
|
||||
decoder.mNumSamplesInput, decoder.mNumSamplesOutput,
|
||||
uint32_t(size_t(decoder.mSizeOfQueue)), uint32_t(decoder.mOutput.Length()),
|
||||
decoder.mWaitingForData, !decoder.HasPromise(), decoder.mLastStreamSourceID);
|
||||
|
||||
if (decoder.mWaitingForData &&
|
||||
(!decoder.mTimeThreshold || decoder.mTimeThreshold.ref().mWaiting)) {
|
||||
// Nothing more we can do at present.
|
||||
LOGV("Still waiting for data.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!needInput) {
|
||||
LOGV("No need for additional input (pending:%u)",
|
||||
uint32_t(decoder.mOutput.Length()));
|
||||
return;
|
||||
}
|
||||
|
||||
needInput = true;
|
||||
|
||||
LOGV("Update(%s) ni=%d no=%d ie=%d, in:%llu out:%llu qs=%u pending:%u ahead:%d sid:%u",
|
||||
TrackTypeToStr(aTrack), needInput, needOutput, decoder.mInputExhausted,
|
||||
decoder.mNumSamplesInput, decoder.mNumSamplesOutput,
|
||||
uint32_t(size_t(decoder.mSizeOfQueue)), uint32_t(decoder.mOutput.Length()),
|
||||
!decoder.HasPromise(), decoder.mLastStreamSourceID);
|
||||
|
||||
// Demux samples if we don't have some.
|
||||
RequestDemuxSamples(aTrack);
|
||||
|
||||
|
@ -127,6 +127,22 @@ private:
|
||||
// Decode any pending already demuxed samples.
|
||||
bool DecodeDemuxedSamples(TrackType aTrack,
|
||||
MediaRawData* aSample);
|
||||
|
||||
struct SeekTarget {
|
||||
SeekTarget(const media::TimeUnit& aTime, bool aDropTarget)
|
||||
: mTime(aTime)
|
||||
, mDropTarget(aDropTarget)
|
||||
, mWaiting(false)
|
||||
{}
|
||||
|
||||
media::TimeUnit mTime;
|
||||
bool mDropTarget;
|
||||
bool mWaiting;
|
||||
};
|
||||
// Perform an internal seek to aTime. If aDropTarget is true then
|
||||
// the first sample past the target will be dropped.
|
||||
void InternalSeek(TrackType aTrack, const SeekTarget& aTarget);
|
||||
|
||||
// Drain the current decoder.
|
||||
void DrainDecoder(TrackType aTrack);
|
||||
void NotifyNewOutput(TrackType aTrack, MediaData* aSample);
|
||||
@ -261,8 +277,11 @@ private:
|
||||
bool mDraining;
|
||||
bool mDrainComplete;
|
||||
// If set, all decoded samples prior mTimeThreshold will be dropped.
|
||||
// Used for internal seeking when a change of stream is detected.
|
||||
Maybe<media::TimeUnit> mTimeThreshold;
|
||||
// Used for internal seeking when a change of stream is detected or when
|
||||
// encountering data discontinuity.
|
||||
Maybe<SeekTarget> mTimeThreshold;
|
||||
// Time of last sample returned.
|
||||
Maybe<media::TimeUnit> mLastSampleTime;
|
||||
|
||||
// Decoded samples returned my mDecoder awaiting being returned to
|
||||
// state machine upon request.
|
||||
@ -300,6 +319,7 @@ private:
|
||||
mDraining = false;
|
||||
mDrainComplete = false;
|
||||
mTimeThreshold.reset();
|
||||
mLastSampleTime.reset();
|
||||
mOutput.Clear();
|
||||
mNumSamplesInput = 0;
|
||||
mNumSamplesOutput = 0;
|
||||
@ -316,6 +336,7 @@ private:
|
||||
uint32_t mLastStreamSourceID;
|
||||
Maybe<uint32_t> mNextStreamSourceID;
|
||||
media::TimeIntervals mTimeRanges;
|
||||
Maybe<media::TimeUnit> mLastTimeRangesEnd;
|
||||
RefPtr<SharedTrackInfo> mInfo;
|
||||
};
|
||||
|
||||
|
@ -480,7 +480,10 @@ DecodedStream::CreateData(MozPromiseHolder<GenericPromise>&& aPromise)
|
||||
self->mOutputStreamManager.Disconnect();
|
||||
delete data;
|
||||
});
|
||||
AbstractThread::MainThread()->Dispatch(r.forget());
|
||||
// We are in tail dispatching phase. Don't call
|
||||
// AbstractThread::MainThread()->Dispatch() to avoid reentrant
|
||||
// AutoTaskDispatcher.
|
||||
NS_DispatchToMainThread(r.forget());
|
||||
}
|
||||
}
|
||||
RefPtr<DecodedStream> mThis;
|
||||
|
@ -4,6 +4,7 @@
|
||||
* 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 "MediaQueue.h"
|
||||
#include "VideoSink.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -295,7 +295,7 @@ MediaSourceTrackDemuxer::MediaSourceTrackDemuxer(MediaSourceDemuxer* aParent,
|
||||
, mManager(aManager)
|
||||
, mType(aType)
|
||||
, mMonitor("MediaSourceTrackDemuxer")
|
||||
, mLastSeek(Some(TimeUnit()))
|
||||
, mReset(true)
|
||||
{
|
||||
}
|
||||
|
||||
@ -328,7 +328,8 @@ MediaSourceTrackDemuxer::Reset()
|
||||
RefPtr<MediaSourceTrackDemuxer> self = this;
|
||||
nsCOMPtr<nsIRunnable> task =
|
||||
NS_NewRunnableFunction([self] () {
|
||||
self->mLastSeek = Some(TimeUnit());
|
||||
self->mNextSample.reset();
|
||||
self->mReset = true;
|
||||
self->mManager->Seek(self->mType, TimeUnit(), TimeUnit());
|
||||
{
|
||||
MonitorAutoLock mon(self->mMonitor);
|
||||
@ -380,42 +381,50 @@ MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime)
|
||||
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ);
|
||||
|
||||
if (!buffered.Contains(aTime)) {
|
||||
mLastSeek = Some(aTime);
|
||||
// We don't have the data to seek to.
|
||||
return SeekPromise::CreateAndReject(DemuxerFailureReason::WAITING_FOR_DATA,
|
||||
__func__);
|
||||
}
|
||||
TimeUnit seekTime =
|
||||
mManager->Seek(mType, aTime, MediaSourceDemuxer::EOS_FUZZ);
|
||||
bool error;
|
||||
RefPtr<MediaRawData> sample =
|
||||
mManager->GetSample(mType,
|
||||
media::TimeUnit(),
|
||||
error);
|
||||
MOZ_ASSERT(!error && sample);
|
||||
mNextSample = Some(sample);
|
||||
mReset = false;
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
mNextRandomAccessPoint = mManager->GetNextRandomAccessPoint(mType);
|
||||
}
|
||||
mLastSeek = Some(aTime);
|
||||
return SeekPromise::CreateAndResolve(seekTime, __func__);
|
||||
}
|
||||
|
||||
RefPtr<MediaSourceTrackDemuxer::SamplesPromise>
|
||||
MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples)
|
||||
{
|
||||
if (mLastSeek) {
|
||||
if (mReset) {
|
||||
// If a seek (or reset) was recently performed, we ensure that the data
|
||||
// we are about to retrieve is still available.
|
||||
TimeIntervals buffered = mManager->Buffered(mType);
|
||||
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ);
|
||||
|
||||
if (!buffered.Contains(mLastSeek.ref())) {
|
||||
if (!buffered.Contains(TimeUnit::FromMicroseconds(0))) {
|
||||
return SamplesPromise::CreateAndReject(
|
||||
mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM :
|
||||
DemuxerFailureReason::WAITING_FOR_DATA, __func__);
|
||||
}
|
||||
mLastSeek.reset();
|
||||
mReset = false;
|
||||
}
|
||||
bool error;
|
||||
RefPtr<MediaRawData> sample =
|
||||
mManager->GetSample(mType,
|
||||
MediaSourceDemuxer::EOS_FUZZ,
|
||||
error);
|
||||
bool error = false;
|
||||
RefPtr<MediaRawData> sample;
|
||||
if (mNextSample) {
|
||||
sample = mNextSample.ref();
|
||||
mNextSample.reset();
|
||||
} else {
|
||||
sample = mManager->GetSample(mType, MediaSourceDemuxer::EOS_FUZZ, error);
|
||||
if (!sample) {
|
||||
if (error) {
|
||||
return SamplesPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__);
|
||||
@ -424,6 +433,7 @@ MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples)
|
||||
mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM :
|
||||
DemuxerFailureReason::WAITING_FOR_DATA, __func__);
|
||||
}
|
||||
}
|
||||
RefPtr<SamplesHolder> samples = new SamplesHolder;
|
||||
samples->mSamples.AppendElement(sample);
|
||||
if (mNextRandomAccessPoint.ToMicroseconds() <= sample->mTime) {
|
||||
|
@ -129,7 +129,10 @@ private:
|
||||
// Monitor protecting members below accessed from multiple threads.
|
||||
Monitor mMonitor;
|
||||
media::TimeUnit mNextRandomAccessPoint;
|
||||
Maybe<media::TimeUnit> mLastSeek;
|
||||
Maybe<RefPtr<MediaRawData>> mNextSample;
|
||||
// Set to true following a reset. Ensure that the next sample demuxed
|
||||
// is available at position 0.
|
||||
bool mReset;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -40,7 +40,9 @@ skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac"))
|
||||
[test_BufferingWait.html]
|
||||
skip-if = toolkit == 'android' #timeout android bug 1199531
|
||||
[test_BufferingWait_mp4.html]
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac") || (os == "win" && os_version == "6.1")) # Only supported on osx and vista+, disabling on win7 bug 1191138
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
|
||||
[test_DrainOnMissingData_mp4.html]
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
|
||||
[test_EndOfStream.html]
|
||||
skip-if = (true || toolkit == 'android' || buildapp == 'mulet') #timeout android/mulet only bug 1101187 and bug 1182946
|
||||
[test_EndOfStream_mp4.html]
|
||||
@ -51,7 +53,7 @@ skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac"))
|
||||
[test_FrameSelection.html]
|
||||
[test_HaveMetadataUnbufferedSeek.html]
|
||||
[test_HaveMetadataUnbufferedSeek_mp4.html]
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac") || (os == "win" && os_version == "6.1")) # Only supported on osx and vista+, disabling on win7 bug 1191138
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
|
||||
[test_LoadedDataFired_mp4.html]
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
|
||||
[test_LoadedMetadataFired.html]
|
||||
@ -98,11 +100,11 @@ skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac"))
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
|
||||
[test_TruncatedDuration.html]
|
||||
[test_TruncatedDuration_mp4.html]
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac") || (os == "win" && os_version == "6.1")) # Only supported on osx and vista+, disabling on win7 bug 1191138
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
|
||||
[test_WaitingOnMissingData.html]
|
||||
skip-if = true # Disabled due to bug 1124493 and friends. WebM MSE is deprioritized.
|
||||
[test_WaitingOnMissingData_mp4.html]
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac") || (os == "win" && os_version == "6.1")) # Only supported on osx and vista+, disabling on win7 bug 1191138
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
|
||||
[test_WaitingToEndedTransition_mp4.html]
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac") || (os == "win" && os_version == "6.1")) # Only supported on osx and vista+, disabling on win7 bug 1191138
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
|
||||
|
||||
|
@ -41,14 +41,15 @@ runWithMSE(function(ms, v) {
|
||||
/* Note - Missing |46712, 67833 - 46712| segment here corresponding to (0.8, 1.2] */
|
||||
/* Note - Missing |67833, 88966 - 67833| segment here corresponding to (1.2, 1.6] */
|
||||
loadSegment.bind(null, sb, new Uint8Array(arrayBuffer, 88966))).then(function() {
|
||||
var promise = waitUntilTime(0.27);
|
||||
// 0.767 is the time of the last video sample +- 40ms.
|
||||
var promise = waitUntilTime(.767-0.04);
|
||||
info("Playing video. It should play for a bit, then fire 'waiting'");
|
||||
v.play();
|
||||
return promise;
|
||||
}).then(function() {
|
||||
window.firstStop = Date.now();
|
||||
loadSegment(sb, new Uint8Array(arrayBuffer, 46712, 67833 - 46712));
|
||||
return waitUntilTime(0.66);
|
||||
return waitUntilTime(1.167-0.04);
|
||||
}).then(function() {
|
||||
var waitDuration = (Date.now() - window.firstStop) / 1000;
|
||||
ok(waitDuration < 15, "Should not spend an inordinate amount of time buffering: " + waitDuration);
|
||||
|
@ -41,18 +41,16 @@ runWithMSE(function(ms, v) {
|
||||
/* Note - Missing |bipbop4| segment here corresponding to (2.41, 3.20] */
|
||||
.then(fetchAndLoad.bind(null, sb, 'bipbop/bipbop', ['5'], '.m4s'))
|
||||
.then(function() {
|
||||
// Some decoders (Windows in particular) may keep up to 25 frames queued
|
||||
// before returning a sample. 0.7 is 1.62s - 25 * 0.03333
|
||||
var promise = waitUntilTime(0.7);
|
||||
// last audio sample has a start time of 1.578956s
|
||||
var promise = waitUntilTime(1.57895);
|
||||
info("Playing video. It should play for a bit, then fire 'waiting'");
|
||||
v.play();
|
||||
return promise;
|
||||
}).then(function() {
|
||||
window.firstStop = Date.now();
|
||||
fetchAndLoad(sb, 'bipbop/bipbop', ['3'], '.m4s');
|
||||
// Some decoders (Windows in particular) may keep up to 25 frames queued
|
||||
// before returning a sample. 1.5 is 2.41s - 25 * 0.03333
|
||||
return waitUntilTime(1.5);
|
||||
// last audio sample has a start time of 2.368435
|
||||
return waitUntilTime(2.36843);
|
||||
}).then(function() {
|
||||
var waitDuration = (Date.now() - window.firstStop) / 1000;
|
||||
ok(waitDuration < 15, "Should not spend an inordinate amount of time buffering: " + waitDuration);
|
||||
|
60
dom/media/mediasource/test/test_DrainOnMissingData_mp4.html
Normal file
60
dom/media/mediasource/test/test_DrainOnMissingData_mp4.html
Normal file
@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=windows-1252">
|
||||
<title>MSE: |waiting| event when source data is missing</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="mediasource.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test"><script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
runWithMSE(function(ms, el) {
|
||||
el.controls = true;
|
||||
once(ms, 'sourceopen').then(function() {
|
||||
ok(true, "Receive a sourceopen event");
|
||||
var videosb = ms.addSourceBuffer("video/mp4");
|
||||
fetchAndLoad(videosb, 'bipbop/bipbop_video', ['init'], '.mp4')
|
||||
.then(function() {
|
||||
// Set appendWindowEnd to ensure we only have about 6 frames worth.
|
||||
// We must feed at least 6 frames to pass the MDSM pre-roll.
|
||||
videosb.appendWindowEnd = .4;
|
||||
return fetchAndLoad(videosb, 'bipbop/bipbop_video', ['1'], '.m4s');
|
||||
})
|
||||
.then(function() {
|
||||
info("Invoking play()");
|
||||
var promises = [];
|
||||
promises.push(once(el, 'playing'));
|
||||
el.play();
|
||||
return Promise.all(promises);
|
||||
})
|
||||
.then(function() {
|
||||
info("got playing");
|
||||
return once(el, 'waiting');
|
||||
}).then(function() {
|
||||
info("got waiting");
|
||||
info("Loading more data");
|
||||
// Waiting will be fired on the last frame +- 40ms.
|
||||
isfuzzy(el.currentTime, videosb.buffered.end(0) - 1/30,
|
||||
0.04, "Got a waiting event at " + el.currentTime);
|
||||
videosb.appendWindowEnd = 1;
|
||||
var p = once(el, 'ended');
|
||||
var loads = fetchAndLoad(videosb, 'bipbop/bipbop_video', [1], '.m4s');
|
||||
loads.then(() => ms.endOfStream());
|
||||
return p;
|
||||
}).then(function() {
|
||||
// These fuzz factors are bigger than they should be. We should investigate
|
||||
// and fix them in bug 1137574.
|
||||
is(el.duration, 0.801666, "Video has correct duration: " + el.duration);
|
||||
is(el.currentTime, el.duration, "Video has correct currentTime.");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -24,6 +24,18 @@ SimpleTest.waitForExplicitFinish();
|
||||
runWithMSE(function(ms, el) {
|
||||
el.controls = true;
|
||||
once(ms, 'sourceopen').then(function() {
|
||||
// Log events for debugging.
|
||||
var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata",
|
||||
"loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort",
|
||||
"waiting", "pause", "durationchange", "seeking", "seeked"];
|
||||
function logEvent(e) {
|
||||
var v = e.target;
|
||||
info("got " + e.type + " event");
|
||||
}
|
||||
events.forEach(function(e) {
|
||||
el.addEventListener(e, logEvent, false);
|
||||
});
|
||||
|
||||
ok(true, "Receive a sourceopen event");
|
||||
var videosb = ms.addSourceBuffer("video/mp4");
|
||||
el.addEventListener("error", function(e) {
|
||||
@ -37,9 +49,6 @@ runWithMSE(function(ms, el) {
|
||||
var promises = [];
|
||||
promises.push(once(el, 'loadeddata'));
|
||||
promises.push(once(el, 'canplay'));
|
||||
// Load [0, 1.601666). We must ensure that we load over 25 frames as the
|
||||
// windows H264 decoder will not produce a sample until then
|
||||
// (bug 1191138).
|
||||
promises.push(fetchAndLoad(videosb, 'bipbop/bipbop_video', range(1, 3), '.m4s'));
|
||||
return Promise.all(promises);
|
||||
})
|
||||
|
@ -40,9 +40,9 @@ runWithMSE(function(ms, el) {
|
||||
// currentTime is based on the current video frame, so if the audio ends just before
|
||||
// the next video frame, currentTime can be up to 1 frame's worth earlier than
|
||||
// min(audioEnd, videoEnd).
|
||||
// Some decoders (Windows in particular) may keep up to 25 frames queued.
|
||||
isfuzzy(el.currentTime, Math.min(audiosb.buffered.end(0), videosb.buffered.end(0)) - 1/60,
|
||||
25 * 1/30, "Got a waiting event at " + el.currentTime);
|
||||
// 0.0465 is the length of the last audio frame.
|
||||
ok(el.currentTime >= (Math.min(audiosb.buffered.end(0), videosb.buffered.end(0)) - 0.0465),
|
||||
"Got a waiting event at " + el.currentTime);
|
||||
info("Loading more data");
|
||||
var p = once(el, 'ended');
|
||||
var loads = Promise.all([fetchAndLoad(audiosb, 'bipbop/bipbop_audio', [5], '.m4s'),
|
||||
|
@ -17,6 +17,10 @@ runWithMSE(function(ms, el) {
|
||||
ok(true, "Receive a sourceopen event");
|
||||
var audiosb = ms.addSourceBuffer("audio/mp4");
|
||||
var videosb = ms.addSourceBuffer("video/mp4");
|
||||
// ensure tracks end at approximately the same time to ensure ended event is
|
||||
// always fired (bug 1233639).
|
||||
audiosb.appendWindowEnd = 3.9;
|
||||
videosb.appendWindowEnd = 3.9;
|
||||
fetchAndLoad(audiosb, 'bipbop/bipbop_audio', ['init'], '.mp4')
|
||||
.then(fetchAndLoad.bind(null, videosb, 'bipbop/bipbop_video', ['init'], '.mp4'))
|
||||
.then(fetchAndLoad.bind(null, audiosb, 'bipbop/bipbop_audio', range(1, 5), '.m4s'))
|
||||
@ -34,19 +38,12 @@ runWithMSE(function(ms, el) {
|
||||
var p = once(el, 'waiting');
|
||||
el.play();
|
||||
return p;
|
||||
}).then(function() {
|
||||
// currentTime is based on the current video frame, so if the audio ends just before
|
||||
// the next video frame, currentTime can be up to 1 frame's worth earlier than
|
||||
// min(audioEnd, videoEnd).
|
||||
// Some decoders (Windows in particular) may keep up to 25 frames queued.
|
||||
isfuzzy(el.currentTime, Math.min(audiosb.buffered.end(0), videosb.buffered.end(0)) - 1/60,
|
||||
25 * 1/30, "Got a waiting event at " + el.currentTime);
|
||||
}).then(function() {
|
||||
var p = once(el, 'ended');
|
||||
ms.endOfStream();
|
||||
return p;
|
||||
}).then(function() {
|
||||
is(el.duration, 4.005, "Video has correct duration: " + el.duration);
|
||||
is(el.duration, 3.854512, "Video has correct duration: " + el.duration);
|
||||
is(el.currentTime, el.duration, "Video has correct currentTime.");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
@ -226,23 +226,12 @@ OmxDataDecoder::DoAsyncShutdown()
|
||||
mWatchManager.Unwatch(mOmxState, &OmxDataDecoder::OmxStateRunner);
|
||||
mWatchManager.Unwatch(mPortSettingsChanged, &OmxDataDecoder::PortSettingsChanged);
|
||||
|
||||
// Do flush so all port can be returned to client.
|
||||
// Flush to all ports, so all buffers can be returned from component.
|
||||
RefPtr<OmxDataDecoder> self = this;
|
||||
mOmxLayer->SendCommand(OMX_CommandFlush, OMX_ALL, nullptr)
|
||||
->Then(mOmxTaskQueue, __func__,
|
||||
[self] () -> RefPtr<OmxCommandPromise> {
|
||||
LOG("DoAsyncShutdown: flush complete, collecting buffers...");
|
||||
self->CollectBufferPromises(OMX_DirMax)
|
||||
->Then(self->mOmxTaskQueue, __func__,
|
||||
[self] () {
|
||||
LOG("DoAsyncShutdown: releasing all buffers.");
|
||||
self->ReleaseBuffers(OMX_DirInput);
|
||||
self->ReleaseBuffers(OMX_DirOutput);
|
||||
},
|
||||
[self] () {
|
||||
self->mOmxLayer->Shutdown();
|
||||
});
|
||||
|
||||
LOG("DoAsyncShutdown: flush complete");
|
||||
return self->mOmxLayer->SendCommand(OMX_CommandStateSet, OMX_StateIdle, nullptr);
|
||||
},
|
||||
[self] () {
|
||||
@ -251,8 +240,30 @@ OmxDataDecoder::DoAsyncShutdown()
|
||||
->CompletionPromise()
|
||||
->Then(mOmxTaskQueue, __func__,
|
||||
[self] () -> RefPtr<OmxCommandPromise> {
|
||||
LOG("DoAsyncShutdown: OMX_StateIdle");
|
||||
return self->mOmxLayer->SendCommand(OMX_CommandStateSet, OMX_StateLoaded, nullptr);
|
||||
RefPtr<OmxCommandPromise> p =
|
||||
self->mOmxLayer->SendCommand(OMX_CommandStateSet, OMX_StateLoaded, nullptr);
|
||||
|
||||
LOG("DoAsyncShutdown: collecting buffers...");
|
||||
self->CollectBufferPromises(OMX_DirMax)
|
||||
->Then(self->mOmxTaskQueue, __func__,
|
||||
[self] () {
|
||||
// According to spec 3.1.1.2.2.1:
|
||||
// OMX_StateLoaded needs to be sent before releasing buffers.
|
||||
// And state transition from OMX_StateIdle to OMX_StateLoaded
|
||||
// is completed when all of the buffers have been removed
|
||||
// from the component.
|
||||
// Here the buffer promises are not resolved due to displaying
|
||||
// in layer, it needs to wait before the layer returns the
|
||||
// buffers.
|
||||
LOG("DoAsyncShutdown: all buffers collected, releasing buffers...");
|
||||
self->ReleaseBuffers(OMX_DirInput);
|
||||
self->ReleaseBuffers(OMX_DirOutput);
|
||||
},
|
||||
[self] () {
|
||||
self->mOmxLayer->Shutdown();
|
||||
});
|
||||
|
||||
return p;
|
||||
},
|
||||
[self] () {
|
||||
self->mOmxLayer->Shutdown();
|
||||
|
@ -196,7 +196,7 @@ MediaEngineTabVideoSource::NotifyPull(MediaStreamGraph*,
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
|
||||
// Note: we're not giving up mImage here
|
||||
RefPtr<layers::CairoImage> image = mImage;
|
||||
RefPtr<layers::SourceSurfaceImage> image = mImage;
|
||||
StreamTime delta = aDesiredTime - aSource->GetEndOfAppendedData(aID);
|
||||
if (delta > 0) {
|
||||
// nullptr images are allowed
|
||||
@ -298,7 +298,7 @@ MediaEngineTabVideoSource::Draw() {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<layers::CairoImage> image = new layers::CairoImage(size, surface);
|
||||
RefPtr<layers::SourceSurfaceImage> image = new layers::SourceSurfaceImage(size, surface);
|
||||
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
mImage = image;
|
||||
|
@ -88,7 +88,7 @@ private:
|
||||
ScopedFreePtr<unsigned char> mData;
|
||||
size_t mDataSize;
|
||||
nsCOMPtr<nsIDOMWindow> mWindow;
|
||||
RefPtr<layers::CairoImage> mImage;
|
||||
RefPtr<layers::SourceSurfaceImage> mImage;
|
||||
nsCOMPtr<nsITimer> mTimer;
|
||||
Monitor mMonitor;
|
||||
nsCOMPtr<nsITabSource> mTabSource;
|
||||
|
@ -957,7 +957,7 @@ PluginInstanceParent::RecvShow(const NPRect& updatedRect,
|
||||
|
||||
RefPtr<gfx::SourceSurface> sourceSurface =
|
||||
gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, surface);
|
||||
RefPtr<CairoImage> image = new CairoImage(surface->GetSize(), sourceSurface);
|
||||
RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surface->GetSize(), sourceSurface);
|
||||
|
||||
nsAutoTArray<ImageContainer::NonOwningImage,1> imageList;
|
||||
imageList.AppendElement(
|
||||
|
@ -139,62 +139,6 @@ CSPService::ShouldLoad(uint32_t aContentType,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// ----- THIS IS A TEMPORARY FAST PATH FOR CERTIFIED APPS. -----
|
||||
// ----- PLEASE REMOVE ONCE bug 925004 LANDS. -----
|
||||
|
||||
// Cache the app status for this origin.
|
||||
uint16_t status = nsIPrincipal::APP_STATUS_NOT_INSTALLED;
|
||||
nsAutoCString sourceOrigin;
|
||||
if (aRequestPrincipal && aRequestOrigin) {
|
||||
aRequestOrigin->GetPrePath(sourceOrigin);
|
||||
if (!mAppStatusCache.Get(sourceOrigin, &status)) {
|
||||
aRequestPrincipal->GetAppStatus(&status);
|
||||
mAppStatusCache.Put(sourceOrigin, status);
|
||||
}
|
||||
}
|
||||
|
||||
if (status == nsIPrincipal::APP_STATUS_CERTIFIED) {
|
||||
// The CSP for certified apps is :
|
||||
// "default-src * data: blob:; script-src 'self'; object-src 'none'; style-src 'self' app://theme.gaiamobile.org:*"
|
||||
// That means we can optimize for this case by:
|
||||
// - loading same origin scripts and stylesheets, and stylesheets from the
|
||||
// theme url space.
|
||||
// - never loading objects.
|
||||
// - accepting everything else.
|
||||
|
||||
switch (aContentType) {
|
||||
case nsIContentPolicy::TYPE_SCRIPT:
|
||||
case nsIContentPolicy::TYPE_STYLESHEET:
|
||||
{
|
||||
// Whitelist the theme resources.
|
||||
auto themeOrigin = Preferences::GetCString("b2g.theme.origin");
|
||||
nsAutoCString contentOrigin;
|
||||
aContentLocation->GetPrePath(contentOrigin);
|
||||
|
||||
if (!(sourceOrigin.Equals(contentOrigin) ||
|
||||
(themeOrigin && themeOrigin.Equals(contentOrigin)))) {
|
||||
*aDecision = nsIContentPolicy::REJECT_SERVER;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case nsIContentPolicy::TYPE_OBJECT:
|
||||
*aDecision = nsIContentPolicy::REJECT_SERVER;
|
||||
break;
|
||||
|
||||
default:
|
||||
*aDecision = nsIContentPolicy::ACCEPT;
|
||||
}
|
||||
|
||||
// Only cache and return if we are successful. If not, we want the error
|
||||
// to be reported, and thus fallback to the slow path.
|
||||
if (*aDecision == nsIContentPolicy::ACCEPT) {
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// ----- END OF TEMPORARY FAST PATH FOR CERTIFIED APPS. -----
|
||||
|
||||
// query the principal of the document; if no document is passed, then
|
||||
// fall back to using the requestPrincipal (e.g. service workers do not
|
||||
// pass a document).
|
||||
|
@ -703,6 +703,8 @@ var interfaceNamesInGlobalScope =
|
||||
"Image",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"ImageBitmap",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"ImageBitmapRenderingContext",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{name: "ImageCapture", disabled: true},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
|
@ -11,7 +11,7 @@
|
||||
*/
|
||||
|
||||
[Constructor,
|
||||
Exposed=(Window,System)]
|
||||
Exposed=(Window,Worker,System)]
|
||||
interface FileReader : EventTarget {
|
||||
// async read methods
|
||||
[Throws]
|
||||
|
@ -23,6 +23,18 @@ interface ImageBitmap {
|
||||
readonly attribute unsigned long height;
|
||||
};
|
||||
|
||||
// It's crucial that there be a way to explicitly dispose of ImageBitmaps
|
||||
// since they refer to potentially large graphics resources. Some uses
|
||||
// of this API proposal will result in repeated allocations of ImageBitmaps,
|
||||
// and garbage collection will not reliably reclaim them quickly enough.
|
||||
// Here we reuse close(), which also exists on another Transferable type,
|
||||
// MessagePort. Potentially, all Transferable types should inherit from a
|
||||
// new interface type "Closeable".
|
||||
partial interface ImageBitmap {
|
||||
// Dispose of all graphical resources associated with this ImageBitmap.
|
||||
void close();
|
||||
};
|
||||
|
||||
[NoInterfaceObject, Exposed=(Window,Worker)]
|
||||
interface ImageBitmapFactories {
|
||||
[Throws]
|
||||
|
36
dom/webidl/ImageBitmapRenderingContext.webidl
Normal file
36
dom/webidl/ImageBitmapRenderingContext.webidl
Normal file
@ -0,0 +1,36 @@
|
||||
/* -*- 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/.
|
||||
*
|
||||
* The origin of this IDL file is
|
||||
* https://wiki.whatwg.org/wiki/OffscreenCanvas
|
||||
*
|
||||
* © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and
|
||||
* Opera Software ASA. You are granted a license to use, reproduce
|
||||
* and create derivative works of this document.
|
||||
*/
|
||||
|
||||
// The new ImageBitmapRenderingContext is a canvas rendering context
|
||||
// which only provides the functionality to replace the canvas's
|
||||
// contents with the given ImageBitmap. Its context id (the first argument
|
||||
// to getContext) is "bitmaprenderer".
|
||||
[Exposed=(Window,Worker)]
|
||||
interface ImageBitmapRenderingContext {
|
||||
// Displays the given ImageBitmap in the canvas associated with this
|
||||
// rendering context. Ownership of the ImageBitmap is transferred to
|
||||
// the canvas. The caller may not use its reference to the ImageBitmap
|
||||
// after making this call. (This semantic is crucial to enable prompt
|
||||
// reclamation of expensive graphics resources, rather than relying on
|
||||
// garbage collection to do so.)
|
||||
//
|
||||
// The ImageBitmap conceptually replaces the canvas's bitmap, but
|
||||
// it does not change the canvas's intrinsic width or height.
|
||||
//
|
||||
// The ImageBitmap, when displayed, is clipped to the rectangle
|
||||
// defined by the canvas's instrinsic width and height. Pixels that
|
||||
// would be covered by the canvas's bitmap which are not covered by
|
||||
// the supplied ImageBitmap are rendered transparent black. Any CSS
|
||||
// styles affecting the display of the canvas are applied as usual.
|
||||
void transferImageBitmap(ImageBitmap bitmap);
|
||||
};
|
@ -5,14 +5,10 @@
|
||||
*
|
||||
* For more information on this interface, please see
|
||||
* https://wiki.whatwg.org/wiki/OffscreenCanvas
|
||||
*
|
||||
* Current implementation focus on transfer canvas from main thread to worker.
|
||||
* So there are some spec doesn't implement, such as [Constructor], toBlob() and
|
||||
* transferToImageBitmap in OffscreenCanvas. Bug 1172796 will implement
|
||||
* remaining spec.
|
||||
*/
|
||||
|
||||
[Exposed=(Window,Worker),
|
||||
[Constructor(unsigned long width, unsigned long height),
|
||||
Exposed=(Window,Worker),
|
||||
Func="mozilla::dom::OffscreenCanvas::PrefEnabled"]
|
||||
interface OffscreenCanvas : EventTarget {
|
||||
[Pure, SetterThrows]
|
||||
@ -23,6 +19,11 @@ interface OffscreenCanvas : EventTarget {
|
||||
[Throws]
|
||||
nsISupports? getContext(DOMString contextId,
|
||||
optional any contextOptions = null);
|
||||
|
||||
ImageBitmap transferToImageBitmap();
|
||||
[Throws]
|
||||
Promise<Blob> toBlob(optional DOMString type = "",
|
||||
optional any encoderOptions);
|
||||
};
|
||||
|
||||
// OffscreenCanvas implements Transferable;
|
||||
|
@ -265,6 +265,7 @@ WEBIDL_FILES = [
|
||||
'IDBTransaction.webidl',
|
||||
'IDBVersionChangeEvent.webidl',
|
||||
'ImageBitmap.webidl',
|
||||
'ImageBitmapRenderingContext.webidl',
|
||||
'ImageCapture.webidl',
|
||||
'ImageData.webidl',
|
||||
'ImageDocument.webidl',
|
||||
|
@ -629,6 +629,7 @@ private:
|
||||
// If one load info cancels or hits an error, it can race with the start
|
||||
// callback coming from another load info.
|
||||
if (mCanceledMainThread || !mCacheCreator) {
|
||||
aRequest->Cancel(NS_ERROR_FAILURE);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
|
29
dom/workers/test/fileapi_chromeScript.js
Normal file
29
dom/workers/test/fileapi_chromeScript.js
Normal file
@ -0,0 +1,29 @@
|
||||
var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
Cu.importGlobalProperties(["File"]);
|
||||
|
||||
var fileNum = 1;
|
||||
|
||||
function createFileWithData(fileData) {
|
||||
var willDelete = fileData === null;
|
||||
var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
|
||||
var testFile = dirSvc.get("ProfD", Ci.nsIFile);
|
||||
testFile.append("fileAPItestfile" + fileNum);
|
||||
fileNum++;
|
||||
var outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
|
||||
outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
|
||||
0666, 0);
|
||||
if (willDelete) {
|
||||
fileData = "some irrelevant test data\n";
|
||||
}
|
||||
outStream.write(fileData, fileData.length);
|
||||
outStream.close();
|
||||
var domFile = new File(testFile);
|
||||
if (willDelete) {
|
||||
testFile.remove(/* recursive: */ false);
|
||||
}
|
||||
return domFile;
|
||||
}
|
||||
|
||||
addMessageListener("files.open", function (message) {
|
||||
sendAsyncMessage("files.opened", message.map(createFileWithData));
|
||||
});
|
@ -119,6 +119,8 @@ support-files =
|
||||
worker_referrer.js
|
||||
websocket_https.html
|
||||
websocket_https_worker.js
|
||||
worker_fileReader.js
|
||||
fileapi_chromeScript.js
|
||||
|
||||
[test_404.html]
|
||||
[test_atob.html]
|
||||
@ -236,3 +238,4 @@ skip-if = (os == "win") || (os == "mac") || toolkit == 'android' #bug 798220
|
||||
[test_referrer.html]
|
||||
[test_sharedWorker_ports.html]
|
||||
[test_sharedWorker_lifetime.html]
|
||||
[test_fileReader.html]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user