зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound. r=merge a=merge CLOSED TREE
This commit is contained in:
Коммит
ca01158c96
|
@ -334,7 +334,6 @@ testing/xpcshell/node-http2/**
|
|||
# Third party services
|
||||
services/common/kinto-http-client.js
|
||||
services/common/kinto-offline-client.js
|
||||
services/sync/tps/extensions/mozmill
|
||||
|
||||
# toolkit/ exclusions
|
||||
|
||||
|
|
|
@ -781,10 +781,6 @@ html|input.urlbar-input[textoverflow]:not([focused]) {
|
|||
margin-inline-end: 0.25em !important;
|
||||
}
|
||||
|
||||
#main-window[customizing] :-moz-any(#urlbar, .searchbar-textbox) > .urlbar-textbox-container > .textbox-input-box {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Flexible spacer sizing (matching url bar) */
|
||||
toolbarpaletteitem[place=toolbar][id^=wrapper-customizableui-special-spring],
|
||||
toolbarspring {
|
||||
|
|
|
@ -82,12 +82,12 @@
|
|||
<xul:button anonid="preferencesButton"
|
||||
label="&preferencesButton.label;"
|
||||
xbl:inherits="value=userContextId"
|
||||
onclick="gContainersPane.onPreferenceClick(event.originalTarget)">
|
||||
oncommand="gContainersPane.onPreferenceCommand(event.originalTarget)">
|
||||
</xul:button>
|
||||
<xul:button anonid="removeButton"
|
||||
label="&removeButton.label;"
|
||||
xbl:inherits="value=userContextId"
|
||||
onclick="gContainersPane.onRemoveClick(event.originalTarget)">
|
||||
oncommand="gContainersPane.onRemoveCommand(event.originalTarget)">
|
||||
</xul:button>
|
||||
</xul:hbox>
|
||||
</xul:hbox>
|
||||
|
|
|
@ -40,7 +40,7 @@ let gContainersPane = {
|
|||
}
|
||||
},
|
||||
|
||||
async onRemoveClick(button) {
|
||||
async onRemoveCommand(button) {
|
||||
let userContextId = parseInt(button.getAttribute("value"), 10);
|
||||
|
||||
let count = ContextualIdentityService.countContainerTabs(userContextId);
|
||||
|
@ -69,11 +69,11 @@ let gContainersPane = {
|
|||
this._rebuildView();
|
||||
},
|
||||
|
||||
onPreferenceClick(button) {
|
||||
onPreferenceCommand(button) {
|
||||
this.openPreferenceDialog(button.getAttribute("value"));
|
||||
},
|
||||
|
||||
onAddButtonClick(button) {
|
||||
onAddButtonCommand(button) {
|
||||
this.openPreferenceDialog(null);
|
||||
},
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
</vbox>
|
||||
<vbox>
|
||||
<hbox flex="1">
|
||||
<button id="containersAdd" onclick="gContainersPane.onAddButtonClick();" accesskey="&addButton.accesskey;" label="&addButton.label;"/>
|
||||
<button id="containersAdd" oncommand="gContainersPane.onAddButtonCommand();" accesskey="&addButton.accesskey;" label="&addButton.label;"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</groupbox>
|
||||
|
|
|
@ -14,7 +14,7 @@ add_task(async function() {
|
|||
let dialogPromise = promiseLoadSubDialog(CONTAINERS_URL);
|
||||
|
||||
let addButton = doc.getElementById("containersAdd");
|
||||
addButton.click();
|
||||
addButton.doCommand();
|
||||
|
||||
let dialog = await dialogPromise;
|
||||
|
||||
|
|
|
@ -249,7 +249,10 @@ add_task(async function testClearHistory() {
|
|||
|
||||
let controller = searchBar.textbox.controllers.getControllerForCommand("cmd_clearhistory");
|
||||
ok(controller.isCommandEnabled("cmd_clearhistory"), "Clear history command enabled");
|
||||
|
||||
let historyCleared = promiseObserver("satchel-storage-changed");
|
||||
controller.doCommand("cmd_clearhistory");
|
||||
await historyCleared;
|
||||
let count = await countEntries();
|
||||
ok(count == 0, "History cleared");
|
||||
});
|
||||
|
@ -262,3 +265,13 @@ add_task(async function asyncCleanup() {
|
|||
gBrowser.selectedBrowser.loadURI("about:blank");
|
||||
await promiseRemoveEngine();
|
||||
});
|
||||
|
||||
function promiseObserver(topic) {
|
||||
return new Promise(resolve => {
|
||||
let obs = (aSubject, aTopic, aData) => {
|
||||
Services.obs.removeObserver(obs, aTopic);
|
||||
resolve(aSubject);
|
||||
};
|
||||
Services.obs.addObserver(obs, topic);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
const {results: Cr, utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
@ -82,26 +82,36 @@ this.Bootstrap = {
|
|||
}
|
||||
|
||||
// record the value of the default branch before setting it
|
||||
switch (realPrefType) {
|
||||
case Services.prefs.PREF_STRING:
|
||||
studyPrefsChanged[prefName] = defaultBranch.getCharPref(prefName);
|
||||
break;
|
||||
try {
|
||||
switch (realPrefType) {
|
||||
case Services.prefs.PREF_STRING:
|
||||
studyPrefsChanged[prefName] = defaultBranch.getCharPref(prefName);
|
||||
break;
|
||||
|
||||
case Services.prefs.PREF_INT:
|
||||
studyPrefsChanged[prefName] = defaultBranch.getIntPref(prefName);
|
||||
break;
|
||||
case Services.prefs.PREF_INT:
|
||||
studyPrefsChanged[prefName] = defaultBranch.getIntPref(prefName);
|
||||
break;
|
||||
|
||||
case Services.prefs.PREF_BOOL:
|
||||
studyPrefsChanged[prefName] = defaultBranch.getBoolPref(prefName);
|
||||
break;
|
||||
case Services.prefs.PREF_BOOL:
|
||||
studyPrefsChanged[prefName] = defaultBranch.getBoolPref(prefName);
|
||||
break;
|
||||
|
||||
case Services.prefs.PREF_INVALID:
|
||||
case Services.prefs.PREF_INVALID:
|
||||
studyPrefsChanged[prefName] = null;
|
||||
break;
|
||||
|
||||
default:
|
||||
// This should never happen
|
||||
log.error(`Error getting startup pref ${prefName}; unknown value type ${experimentPrefType}.`);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.result == Cr.NS_ERROR_UNEXPECTED) {
|
||||
// There is a value for the pref on the user branch but not on the default branch. This is ok.
|
||||
studyPrefsChanged[prefName] = null;
|
||||
break;
|
||||
|
||||
default:
|
||||
// This should never happen
|
||||
log.error(`Error getting startup pref ${prefName}; unknown value type ${experimentPrefType}.`);
|
||||
} else {
|
||||
// rethrow
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// now set the new default value
|
||||
|
|
|
@ -252,3 +252,19 @@ decorate_task(
|
|||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Test that startup prefs are handled correctly when there is a value on the user branch but not the default branch.
|
||||
decorate_task(
|
||||
withPrefEnv({
|
||||
set: [
|
||||
["extensions.shield-recipe-client.startupExperimentPrefs.testing.does-not-exist", "foo"],
|
||||
["testing.does-not-exist", "foo"],
|
||||
],
|
||||
}),
|
||||
withBootstrap,
|
||||
withStub(PreferenceExperiments, "recordOriginalValues"),
|
||||
async function testInitExperimentPrefsNoDefaultValue(Bootstrap) {
|
||||
Bootstrap.initExperimentPrefs();
|
||||
ok(true, "initExperimentPrefs should not throw for non-existant prefs");
|
||||
},
|
||||
);
|
||||
|
|
|
@ -69,6 +69,10 @@
|
|||
margin-inline-end: 0;
|
||||
}
|
||||
|
||||
:root[customizing] .urlbar-input-box {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#urlbar-container {
|
||||
-moz-box-align: center;
|
||||
}
|
||||
|
|
|
@ -119,8 +119,7 @@ else
|
|||
esac
|
||||
export CCACHE="$topsrcdir/sccache2/sccache${suffix}"
|
||||
export SCCACHE_VERBOSE_STATS=1
|
||||
mk_add_options MOZ_PREFLIGHT_ALL+=build/sccache.mk
|
||||
mk_add_options MOZ_POSTFLIGHT_ALL+=build/sccache.mk
|
||||
mk_add_options MOZBUILD_MANAGE_SCCACHE_DAEMON=${topsrcdir}/sccache2/sccache
|
||||
mk_add_options "UPLOAD_EXTRA_FILES+=sccache.log.gz"
|
||||
case "$platform" in
|
||||
win*)
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
preflight_all:
|
||||
# Terminate any sccache server that might still be around
|
||||
-$(TOPSRCDIR)/sccache2/sccache --stop-server > /dev/null 2>&1
|
||||
# Start a new server, ensuring it gets the jobserver file descriptors
|
||||
# from make (but don't use the + prefix when make -n is used, so that
|
||||
# the command doesn't run in that case)
|
||||
$(if $(findstring n,$(filter-out --%, $(MAKEFLAGS))),,+)env RUST_LOG=sccache::compiler=debug SCCACHE_ERROR_LOG=$(OBJDIR)/dist/sccache.log $(TOPSRCDIR)/sccache2/sccache --start-server
|
||||
|
||||
postflight_all:
|
||||
# Terminate sccache server. This prints sccache stats.
|
||||
-$(TOPSRCDIR)/sccache2/sccache --stop-server
|
43
client.mk
43
client.mk
|
@ -12,14 +12,10 @@
|
|||
# Options:
|
||||
# MOZ_OBJDIR - Destination object directory
|
||||
# MOZ_MAKE_FLAGS - Flags to pass to $(MAKE)
|
||||
# MOZ_PREFLIGHT_ALL - Makefiles to run before building.
|
||||
# MOZ_POSTFLIGHT_ALL - Makefiles to run after building.
|
||||
#
|
||||
#######################################################################
|
||||
# Defines
|
||||
|
||||
comma := ,
|
||||
|
||||
ifdef MACH
|
||||
ifndef NO_BUILDSTATUS_MESSAGES
|
||||
define BUILDSTATUS
|
||||
|
@ -44,8 +40,6 @@ TOPSRCDIR := $(CWD)
|
|||
endif
|
||||
endif
|
||||
|
||||
SH := /bin/sh
|
||||
PERL ?= perl
|
||||
PYTHON ?= $(shell which python2.7 > /dev/null 2>&1 && echo python2.7 || echo python)
|
||||
|
||||
CONFIG_GUESS := $(shell $(TOPSRCDIR)/build/autoconf/config.guess)
|
||||
|
@ -153,15 +147,16 @@ endif
|
|||
# helper target for mobile
|
||||
build_and_deploy: build package install
|
||||
|
||||
#####################################################
|
||||
# Preflight, before building any project
|
||||
|
||||
ifdef MOZ_PREFLIGHT_ALL
|
||||
build preflight_all::
|
||||
set -e; \
|
||||
for mkfile in $(MOZ_PREFLIGHT_ALL); do \
|
||||
$(MAKE) -f $(TOPSRCDIR)/$$mkfile preflight_all TOPSRCDIR=$(TOPSRCDIR) OBJDIR=$(OBJDIR) MOZ_OBJDIR=$(MOZ_OBJDIR); \
|
||||
done
|
||||
# In automation, manage an sccache daemon. The starting of the server
|
||||
# needs to be in a make file so sccache inherits the jobserver.
|
||||
ifdef MOZBUILD_MANAGE_SCCACHE_DAEMON
|
||||
build::
|
||||
# Terminate any sccache server that might still be around.
|
||||
-$(MOZBUILD_MANAGE_SCCACHE_DAEMON) --stop-server > /dev/null 2>&1
|
||||
# Start a new server, ensuring it gets the jobserver file descriptors
|
||||
# from make (but don't use the + prefix when make -n is used, so that
|
||||
# the command doesn't run in that case)
|
||||
$(if $(findstring n,$(filter-out --%, $(MAKEFLAGS))),,+)env RUST_LOG=sccache::compiler=debug SCCACHE_ERROR_LOG=$(OBJDIR)/dist/sccache.log $(MOZBUILD_MANAGE_SCCACHE_DAEMON) --start-server
|
||||
endif
|
||||
|
||||
####################################
|
||||
|
@ -280,25 +275,17 @@ build:: $(OBJDIR)/Makefile $(OBJDIR)/config.status
|
|||
$(OBJDIR_TARGETS):: $(OBJDIR)/Makefile $(OBJDIR)/config.status
|
||||
+$(MOZ_MAKE) $@
|
||||
|
||||
####################################
|
||||
# Postflight, after building all projects
|
||||
|
||||
ifdef MOZ_AUTOMATION
|
||||
build::
|
||||
$(MAKE) -f $(TOPSRCDIR)/client.mk automation/build
|
||||
endif
|
||||
|
||||
ifdef MOZ_POSTFLIGHT_ALL
|
||||
build postflight_all::
|
||||
set -e; \
|
||||
for mkfile in $(MOZ_POSTFLIGHT_ALL); do \
|
||||
$(MAKE) -f $(TOPSRCDIR)/$$mkfile postflight_all TOPSRCDIR=$(TOPSRCDIR) OBJDIR=$(OBJDIR) MOZ_OBJDIR=$(MOZ_OBJDIR); \
|
||||
done
|
||||
ifdef MOZBUILD_MANAGE_SCCACHE_DAEMON
|
||||
build::
|
||||
# Terminate sccache server. This prints sccache stats.
|
||||
-$(MOZBUILD_MANAGE_SCCACHE_DAEMON) --stop-server
|
||||
endif
|
||||
|
||||
echo-variable-%:
|
||||
@echo $($*)
|
||||
|
||||
# This makefile doesn't support parallel execution. It does pass
|
||||
# MOZ_MAKE_FLAGS to sub-make processes, so they will correctly execute
|
||||
# in parallel.
|
||||
|
@ -307,6 +294,4 @@ echo-variable-%:
|
|||
.PHONY: \
|
||||
build \
|
||||
configure \
|
||||
preflight_all \
|
||||
postflight_all \
|
||||
$(OBJDIR_TARGETS)
|
||||
|
|
|
@ -59,8 +59,7 @@ endif
|
|||
.PHONY: FORCE
|
||||
|
||||
# Extra define to trigger some workarounds. We should strive to limit the
|
||||
# use of those. As of writing the only ones are in
|
||||
# toolkit/content/buildconfig.html and browser/locales/jar.mn.
|
||||
# use of those. As of writing the only one is in browser/locales/jar.mn.
|
||||
ACDEFINES += -DBUILD_FASTER
|
||||
|
||||
# Files under the faster/ sub-directory, however, are not meant to use the
|
||||
|
|
|
@ -5,37 +5,61 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
add_task(function* () {
|
||||
info("Test JSON row selection started");
|
||||
add_task(async function () {
|
||||
info("Test 1 JSON row selection started");
|
||||
|
||||
// Create a tall JSON so that there is a scrollbar.
|
||||
let numRows = 1e3;
|
||||
let json = JSON.stringify(Array(numRows).fill().map((_, i) => i));
|
||||
let tab = yield addJsonViewTab("data:application/json," + json);
|
||||
let tab = await addJsonViewTab("data:application/json," + json);
|
||||
|
||||
is(yield getElementCount(".treeRow"), numRows, "Got the expected number of rows.");
|
||||
yield assertRowSelected(null);
|
||||
yield evalInContent("var scroller = $('.jsonPanelBox .panelContent')");
|
||||
ok(yield evalInContent("scroller.clientHeight < scroller.scrollHeight"),
|
||||
is(await getElementCount(".treeRow"), numRows, "Got the expected number of rows.");
|
||||
await assertRowSelected(null);
|
||||
await evalInContent("var scroller = $('.jsonPanelBox .panelContent')");
|
||||
ok(await evalInContent("scroller.clientHeight < scroller.scrollHeight"),
|
||||
"There is a scrollbar.");
|
||||
is(yield evalInContent("scroller.scrollTop"), 0, "Initially scrolled to the top.");
|
||||
is(await evalInContent("scroller.scrollTop"), 0, "Initially scrolled to the top.");
|
||||
|
||||
// Click to select last row.
|
||||
yield evalInContent("$('.treeRow:last-child').click()");
|
||||
yield assertRowSelected(numRows);
|
||||
is(yield evalInContent("scroller.scrollTop + scroller.clientHeight"),
|
||||
yield evalInContent("scroller.scrollHeight"), "Scrolled to the bottom.");
|
||||
await evalInContent("$('.treeRow:last-child').click()");
|
||||
await assertRowSelected(numRows);
|
||||
is(await evalInContent("scroller.scrollTop + scroller.clientHeight"),
|
||||
await evalInContent("scroller.scrollHeight"), "Scrolled to the bottom.");
|
||||
|
||||
// Click to select 2nd row.
|
||||
yield evalInContent("$('.treeRow:nth-child(2)').click()");
|
||||
yield assertRowSelected(2);
|
||||
ok(yield evalInContent("scroller.scrollTop > 0"), "Not scrolled to the top.");
|
||||
await evalInContent("$('.treeRow:nth-child(2)').click()");
|
||||
await assertRowSelected(2);
|
||||
ok(await evalInContent("scroller.scrollTop > 0"), "Not scrolled to the top.");
|
||||
|
||||
// Synthetize up arrow key to select first row.
|
||||
yield evalInContent("$('.treeTable').focus()");
|
||||
yield BrowserTestUtils.synthesizeKey("VK_UP", {}, tab.linkedBrowser);
|
||||
yield assertRowSelected(1);
|
||||
is(yield evalInContent("scroller.scrollTop"), 0, "Scrolled to the top.");
|
||||
await evalInContent("$('.treeTable').focus()");
|
||||
await BrowserTestUtils.synthesizeKey("VK_UP", {}, tab.linkedBrowser);
|
||||
await assertRowSelected(1);
|
||||
is(await evalInContent("scroller.scrollTop"), 0, "Scrolled to the top.");
|
||||
});
|
||||
|
||||
add_task(async function () {
|
||||
info("Test 2 JSON row selection started");
|
||||
|
||||
let numRows = 4;
|
||||
let tab = await addJsonViewTab("data:application/json,[0,1,2,3]");
|
||||
|
||||
is(await getElementCount(".treeRow"), numRows, "Got the expected number of rows.");
|
||||
await assertRowSelected(null);
|
||||
|
||||
// Click to select first row.
|
||||
await clickJsonNode(".treeRow:first-child");
|
||||
await assertRowSelected(1);
|
||||
|
||||
// Synthetize multiple down arrow keydowns to select following rows.
|
||||
for (let i = 2; i < numRows; ++i) {
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {type: "keydown"}, tab.linkedBrowser);
|
||||
await assertRowSelected(i);
|
||||
}
|
||||
|
||||
// Now synthetize the keyup, this shouldn't change selected row.
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {type: "keyup"}, tab.linkedBrowser);
|
||||
await assertRowSelected(numRows - 1);
|
||||
});
|
||||
|
||||
async function assertRowSelected(rowNum) {
|
||||
|
|
|
@ -32,54 +32,47 @@ registerCleanupFunction(() => {
|
|||
* will be considered to have failed, and the returned promise will be rejected.
|
||||
* If this parameter is not passed or is negative, it will be ignored.
|
||||
*/
|
||||
function addJsonViewTab(url, timeout = -1) {
|
||||
async function addJsonViewTab(url, timeout = -1) {
|
||||
info("Adding a new JSON tab with URL: '" + url + "'");
|
||||
|
||||
let deferred = defer();
|
||||
addTab(url).then(tab => {
|
||||
let browser = tab.linkedBrowser;
|
||||
let tab = await addTab(url);
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
// Load devtools/shared/frame-script-utils.js
|
||||
getFrameScript();
|
||||
// Load devtools/shared/frame-script-utils.js
|
||||
getFrameScript();
|
||||
|
||||
// Load frame script with helpers for JSON View tests.
|
||||
let rootDir = getRootDirectory(gTestPath);
|
||||
let frameScriptUrl = rootDir + "doc_frame_script.js";
|
||||
browser.messageManager.loadFrameScript(frameScriptUrl, false);
|
||||
// Load frame script with helpers for JSON View tests.
|
||||
let rootDir = getRootDirectory(gTestPath);
|
||||
let frameScriptUrl = rootDir + "doc_frame_script.js";
|
||||
browser.messageManager.loadFrameScript(frameScriptUrl, false);
|
||||
|
||||
// Check if there is a JSONView object.
|
||||
if (!content.window.wrappedJSObject.JSONView) {
|
||||
deferred.reject("JSON Viewer did not load.");
|
||||
return;
|
||||
}
|
||||
// Check if there is a JSONView object.
|
||||
if (!content.window.wrappedJSObject.JSONView) {
|
||||
throw new Error("JSON Viewer did not load.");
|
||||
}
|
||||
|
||||
// Resolve if the JSONView is fully loaded or wait
|
||||
// for an initialization event.
|
||||
if (content.window.wrappedJSObject.JSONView.initialized) {
|
||||
deferred.resolve(tab);
|
||||
} else {
|
||||
waitForContentMessage("Test:JsonView:JSONViewInitialized").then(() => {
|
||||
deferred.resolve(tab);
|
||||
});
|
||||
}
|
||||
// Resolve if the JSONView is fully loaded.
|
||||
if (content.window.wrappedJSObject.JSONView.initialized) {
|
||||
return tab;
|
||||
}
|
||||
|
||||
// Add a timeout.
|
||||
if (timeout >= 0) {
|
||||
new Promise(resolve => {
|
||||
if (content.window.document.readyState === "complete") {
|
||||
resolve();
|
||||
} else {
|
||||
waitForContentMessage("Test:JsonView:load").then(resolve);
|
||||
}
|
||||
}).then(() => {
|
||||
setTimeout(() => {
|
||||
deferred.reject("JSON Viewer did not load.");
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
});
|
||||
// Otherwise wait for an initialization event, possibly with a time limit.
|
||||
const onJSONViewInitialized =
|
||||
waitForContentMessage("Test:JsonView:JSONViewInitialized")
|
||||
.then(() => tab);
|
||||
|
||||
return deferred.promise;
|
||||
if (!(timeout >= 0)) {
|
||||
return onJSONViewInitialized;
|
||||
}
|
||||
|
||||
if (content.window.document.readyState !== "complete") {
|
||||
await waitForContentMessage("Test:JsonView:load");
|
||||
}
|
||||
|
||||
let onTimeout = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error("JSON Viewer did not load.")), timeout));
|
||||
|
||||
return Promise.race([onJSONViewInitialized, onTimeout]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -165,9 +158,7 @@ function sendString(str, selector) {
|
|||
}
|
||||
|
||||
function waitForTime(delay) {
|
||||
let deferred = defer();
|
||||
setTimeout(deferred.resolve, delay);
|
||||
return deferred.promise;
|
||||
return new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
|
||||
function waitForFilter() {
|
||||
|
|
|
@ -130,7 +130,6 @@ define(function (require, exports, module) {
|
|||
this.toggle = this.toggle.bind(this);
|
||||
this.isExpanded = this.isExpanded.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.onKeyUp = this.onKeyUp.bind(this);
|
||||
this.onClickRow = this.onClickRow.bind(this);
|
||||
this.getSelectedRow = this.getSelectedRow.bind(this);
|
||||
this.selectRow = this.selectRow.bind(this);
|
||||
|
@ -228,13 +227,10 @@ define(function (require, exports, module) {
|
|||
// Event Handlers
|
||||
|
||||
onKeyDown(event) {
|
||||
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(
|
||||
event.key)) {
|
||||
event.preventDefault();
|
||||
if (!["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onKeyUp(event) {
|
||||
let row = this.getSelectedRow(this.rows);
|
||||
if (!row) {
|
||||
return;
|
||||
|
@ -265,8 +261,6 @@ define(function (require, exports, module) {
|
|||
this.selectRow(previousRow);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
@ -471,7 +465,6 @@ define(function (require, exports, module) {
|
|||
role: "tree",
|
||||
tabIndex: 0,
|
||||
onKeyDown: this.onKeyDown,
|
||||
onKeyUp: this.onKeyUp,
|
||||
"aria-label": this.props.label || "",
|
||||
"aria-activedescendant": this.state.selected,
|
||||
cellPadding: 0,
|
||||
|
|
|
@ -428,6 +428,17 @@ DevToolsStartup.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the user is a DevTools user by looking at our selfxss pref.
|
||||
* This preference is incremented everytime the console is used (up to 5).
|
||||
*
|
||||
* @return {Boolean} true if the user can be considered as a devtools user.
|
||||
*/
|
||||
isDevToolsUser() {
|
||||
let selfXssCount = Services.prefs.getIntPref("devtools.selfxss.count", 0);
|
||||
return selfXssCount > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Depending on some runtime parameters (command line arguments as well as existing
|
||||
* preferences), the DEVTOOLS_ENABLED_PREF might be forced to true.
|
||||
|
@ -442,7 +453,8 @@ DevToolsStartup.prototype = {
|
|||
}
|
||||
|
||||
let hasToolbarPref = Services.prefs.getBoolPref(TOOLBAR_VISIBLE_PREF, false);
|
||||
if (hasDevToolsFlag || hasToolbarPref) {
|
||||
|
||||
if (hasDevToolsFlag || hasToolbarPref || this.isDevToolsUser()) {
|
||||
Services.prefs.setBoolPref(DEVTOOLS_ENABLED_PREF, true);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -87,10 +87,11 @@ protected:
|
|||
MOZ_RELEASE_ASSERT(aContainer,
|
||||
"This constructor shouldn't be used when pointing nowhere");
|
||||
if (!mRef) {
|
||||
MOZ_ASSERT(mOffset.value() == 0);
|
||||
MOZ_ASSERT(!mParent->IsContainerNode() || mOffset.value() == 0);
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mOffset.value() > 0);
|
||||
MOZ_ASSERT(mParent == mRef->GetParentNode());
|
||||
MOZ_ASSERT(mParent->GetChildAt(mOffset.value() - 1) == mRef);
|
||||
}
|
||||
|
||||
|
|
|
@ -49,19 +49,10 @@ HTMLOptionElement::~HTMLOptionElement()
|
|||
{
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED(HTMLOptionElement, nsGenericHTMLElement,
|
||||
nsIDOMHTMLOptionElement)
|
||||
NS_IMPL_ISUPPORTS_INHERITED0(HTMLOptionElement, nsGenericHTMLElement)
|
||||
|
||||
NS_IMPL_ELEMENT_CLONE(HTMLOptionElement)
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLOptionElement::GetForm(nsIDOMHTMLFormElement** aForm)
|
||||
{
|
||||
NS_IF_ADDREF(*aForm = GetForm());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mozilla::dom::HTMLFormElement*
|
||||
HTMLOptionElement::GetForm()
|
||||
{
|
||||
|
@ -115,15 +106,7 @@ HTMLOptionElement::UpdateDisabledState(bool aNotify)
|
|||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLOptionElement::GetSelected(bool* aValue)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aValue);
|
||||
*aValue = Selected();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
void
|
||||
HTMLOptionElement::SetSelected(bool aValue)
|
||||
{
|
||||
// Note: The select content obj maintains all the PresState
|
||||
|
@ -141,21 +124,6 @@ HTMLOptionElement::SetSelected(bool aValue)
|
|||
} else {
|
||||
SetSelectedInternal(aValue, true);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_BOOL_ATTR(HTMLOptionElement, DefaultSelected, selected)
|
||||
// GetText returns a whitespace compressed .textContent value.
|
||||
NS_IMPL_STRING_ATTR_WITH_FALLBACK(HTMLOptionElement, Label, label, GetText)
|
||||
NS_IMPL_STRING_ATTR_WITH_FALLBACK(HTMLOptionElement, Value, value, GetText)
|
||||
NS_IMPL_BOOL_ATTR(HTMLOptionElement, Disabled, disabled)
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLOptionElement::GetIndex(int32_t* aIndex)
|
||||
{
|
||||
*aIndex = Index();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
int32_t
|
||||
|
@ -179,18 +147,6 @@ HTMLOptionElement::Index()
|
|||
return index;
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLOptionElement::Selected() const
|
||||
{
|
||||
return mIsSelected;
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLOptionElement::DefaultSelected() const
|
||||
{
|
||||
return HasAttr(kNameSpaceID_None, nsGkAtoms::selected);
|
||||
}
|
||||
|
||||
nsChangeHint
|
||||
HTMLOptionElement::GetAttributeChangeHint(const nsAtom* aAttribute,
|
||||
int32_t aModType) const
|
||||
|
@ -288,7 +244,7 @@ HTMLOptionElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
|
|||
aValue, aOldValue, aSubjectPrincipal, aNotify);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
void
|
||||
HTMLOptionElement::GetText(nsAString& aText)
|
||||
{
|
||||
nsAutoString text;
|
||||
|
@ -310,14 +266,12 @@ HTMLOptionElement::GetText(nsAString& aText)
|
|||
// XXX No CompressWhitespace for nsAString. Sad.
|
||||
text.CompressWhitespace(true, true);
|
||||
aText = text;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLOptionElement::SetText(const nsAString& aText)
|
||||
void
|
||||
HTMLOptionElement::SetText(const nsAString& aText, ErrorResult& aRv)
|
||||
{
|
||||
return nsContentUtils::SetNodeTextContent(this, aText, true);
|
||||
aRv = nsContentUtils::SetNodeTextContent(this, aText, true);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -435,11 +389,7 @@ HTMLOptionElement::Option(const GlobalObject& aGlobal,
|
|||
}
|
||||
}
|
||||
|
||||
option->SetSelected(aSelected, aError);
|
||||
if (aError.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
option->SetSelected(aSelected);
|
||||
option->SetSelectedChanged(false);
|
||||
|
||||
return option.forget();
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "nsGenericHTMLElement.h"
|
||||
#include "nsIDOMHTMLOptionElement.h"
|
||||
#include "mozilla/dom/HTMLFormElement.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -17,8 +16,7 @@ namespace dom {
|
|||
|
||||
class HTMLSelectElement;
|
||||
|
||||
class HTMLOptionElement final : public nsGenericHTMLElement,
|
||||
public nsIDOMHTMLOptionElement
|
||||
class HTMLOptionElement final : public nsGenericHTMLElement
|
||||
{
|
||||
public:
|
||||
explicit HTMLOptionElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
|
||||
|
@ -36,13 +34,15 @@ public:
|
|||
// nsISupports
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
// nsIDOMHTMLOptionElement
|
||||
using mozilla::dom::Element::SetText;
|
||||
using mozilla::dom::Element::GetText;
|
||||
NS_DECL_NSIDOMHTMLOPTIONELEMENT
|
||||
|
||||
bool Selected() const;
|
||||
bool DefaultSelected() const;
|
||||
bool Selected() const
|
||||
{
|
||||
return mIsSelected;
|
||||
}
|
||||
void SetSelected(bool aValue);
|
||||
|
||||
|
||||
void SetSelectedChanged(bool aValue)
|
||||
{
|
||||
|
@ -106,35 +106,39 @@ public:
|
|||
|
||||
HTMLFormElement* GetForm();
|
||||
|
||||
// The XPCOM GetLabel is OK for us
|
||||
void GetLabel(DOMString& aLabel)
|
||||
{
|
||||
if (!GetAttr(kNameSpaceID_None, nsGkAtoms::label, aLabel)) {
|
||||
GetText(aLabel);
|
||||
}
|
||||
}
|
||||
void SetLabel(const nsAString& aLabel, ErrorResult& aError)
|
||||
{
|
||||
SetHTMLAttr(nsGkAtoms::label, aLabel, aError);
|
||||
}
|
||||
|
||||
// The XPCOM DefaultSelected is OK for us
|
||||
bool DefaultSelected() const
|
||||
{
|
||||
return HasAttr(kNameSpaceID_None, nsGkAtoms::selected);
|
||||
}
|
||||
void SetDefaultSelected(bool aValue, ErrorResult& aRv)
|
||||
{
|
||||
SetHTMLBoolAttr(nsGkAtoms::selected, aValue, aRv);
|
||||
}
|
||||
|
||||
// The XPCOM Selected is OK for us
|
||||
void SetSelected(bool aValue, ErrorResult& aRv)
|
||||
void GetValue(nsAString& aValue)
|
||||
{
|
||||
aRv = SetSelected(aValue);
|
||||
if (!GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue)) {
|
||||
GetText(aValue);
|
||||
}
|
||||
}
|
||||
|
||||
// The XPCOM GetValue is OK for us
|
||||
void SetValue(const nsAString& aValue, ErrorResult& aRv)
|
||||
{
|
||||
SetHTMLAttr(nsGkAtoms::value, aValue, aRv);
|
||||
}
|
||||
|
||||
// The XPCOM GetText is OK for us
|
||||
void SetText(const nsAString& aValue, ErrorResult& aRv)
|
||||
{
|
||||
aRv = SetText(aValue);
|
||||
}
|
||||
void GetText(nsAString& aText);
|
||||
void SetText(const nsAString& aText, ErrorResult& aRv);
|
||||
|
||||
int32_t Index();
|
||||
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
#include "nsGenericHTMLElement.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
class nsIDOMHTMLOptionElement;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
|
|
|
@ -513,7 +513,7 @@ HTMLSelectElement::GetFirstOptionIndex(nsIContent* aOptions)
|
|||
int32_t listIndex = -1;
|
||||
HTMLOptionElement* optElement = HTMLOptionElement::FromContent(aOptions);
|
||||
if (optElement) {
|
||||
GetOptionIndex(optElement, 0, true, &listIndex);
|
||||
mOptions->GetOptionIndex(optElement->AsElement(), 0, true, &listIndex);
|
||||
return listIndex;
|
||||
}
|
||||
|
||||
|
@ -711,15 +711,6 @@ HTMLSelectElement::SetSelectedIndexInternal(int32_t aIndex, bool aNotify)
|
|||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLSelectElement::GetOptionIndex(nsIDOMHTMLOptionElement* aOption,
|
||||
int32_t aStartIndex, bool aForward,
|
||||
int32_t* aIndex)
|
||||
{
|
||||
nsCOMPtr<nsINode> option = do_QueryInterface(aOption);
|
||||
return mOptions->GetOptionIndex(option->AsElement(), aStartIndex, aForward, aIndex);
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLSelectElement::IsOptionSelectedByIndex(int32_t aIndex)
|
||||
{
|
||||
|
@ -1032,8 +1023,7 @@ HTMLSelectElement::GetValue(DOMString& aValue)
|
|||
return;
|
||||
}
|
||||
|
||||
DebugOnly<nsresult> rv = option->GetValue(aValue);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
option->GetValue(aValue);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1479,8 +1469,8 @@ HTMLSelectElement::RestoreStateTo(SelectState* aNewSelected)
|
|||
HTMLOptionElement* option = Item(i);
|
||||
if (option) {
|
||||
nsAutoString value;
|
||||
nsresult rv = option->GetValue(value);
|
||||
if (NS_SUCCEEDED(rv) && aNewSelected->ContainsOption(i, value)) {
|
||||
option->GetValue(value);
|
||||
if (aNewSelected->ContainsOption(i, value)) {
|
||||
SetOptionsSelectedByIndex(i, i, IS_SELECTED | SET_DISABLED | NOTIFY);
|
||||
}
|
||||
}
|
||||
|
@ -1579,7 +1569,7 @@ HTMLSelectElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission)
|
|||
}
|
||||
|
||||
nsString value;
|
||||
MOZ_ALWAYS_SUCCEEDS(option->GetValue(value));
|
||||
option->GetValue(value);
|
||||
|
||||
if (keyGenProcessor) {
|
||||
nsString tmp(value);
|
||||
|
@ -1659,7 +1649,7 @@ HTMLSelectElement::IsValueMissing() const
|
|||
// Check for a placeholder label option, don't count it as a valid value.
|
||||
if (i == 0 && !Multiple() && Size() <= 1 && option->GetParent() == this) {
|
||||
nsAutoString value;
|
||||
MOZ_ALWAYS_SUCCEEDS(option->GetValue(value));
|
||||
option->GetValue(value);
|
||||
if (value.IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -360,19 +360,6 @@ public:
|
|||
int32_t aEndIndex,
|
||||
uint32_t aOptionsMask);
|
||||
|
||||
/**
|
||||
* Finds the index of a given option element
|
||||
*
|
||||
* @param aOption the option to get the index of
|
||||
* @param aStartIndex the index to start looking at
|
||||
* @param aForward TRUE to look forward, FALSE to look backward
|
||||
* @return the option index
|
||||
*/
|
||||
NS_IMETHOD GetOptionIndex(nsIDOMHTMLOptionElement* aOption,
|
||||
int32_t aStartIndex,
|
||||
bool aForward,
|
||||
int32_t* aIndex);
|
||||
|
||||
/**
|
||||
* Called when an attribute is about to be changed
|
||||
*/
|
||||
|
|
|
@ -16,7 +16,6 @@ XPIDL_SOURCES += [
|
|||
'nsIDOMHTMLHtmlElement.idl',
|
||||
'nsIDOMHTMLInputElement.idl',
|
||||
'nsIDOMHTMLMediaElement.idl',
|
||||
'nsIDOMHTMLOptionElement.idl',
|
||||
'nsIDOMHTMLScriptElement.idl',
|
||||
'nsIDOMMozBrowserFrame.idl',
|
||||
'nsIDOMTimeRanges.idl',
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsIDOMHTMLElement.idl"
|
||||
|
||||
/**
|
||||
* The nsIDOMHTMLOptionElement interface is the interface to a [X]HTML
|
||||
* option element.
|
||||
*
|
||||
* This interface is trying to follow the DOM Level 2 HTML specification:
|
||||
* http://www.w3.org/TR/DOM-Level-2-HTML/
|
||||
*
|
||||
* with changes from the work-in-progress WHATWG HTML specification:
|
||||
* http://www.whatwg.org/specs/web-apps/current-work/
|
||||
*/
|
||||
|
||||
[uuid(c2b3e9ff-6b36-4158-ace3-05a9c5b8e1c1)]
|
||||
interface nsIDOMHTMLOptionElement : nsISupports
|
||||
{
|
||||
attribute boolean disabled;
|
||||
readonly attribute nsIDOMHTMLFormElement form;
|
||||
attribute DOMString label;
|
||||
attribute boolean defaultSelected;
|
||||
attribute boolean selected;
|
||||
attribute DOMString value;
|
||||
|
||||
attribute DOMString text;
|
||||
readonly attribute long index;
|
||||
};
|
|
@ -233,13 +233,13 @@ class MediaRecorder::Session: public PrincipalChangeObserver<MediaStreamTrack>,
|
|||
}
|
||||
|
||||
if (NS_FAILED(aRv)) {
|
||||
recorder->NotifyError(aRv);
|
||||
mSession->DoSessionEndTask(aRv);
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv = recorder->CreateAndDispatchBlobEvent(aBlob);
|
||||
if (NS_FAILED(rv)) {
|
||||
recorder->NotifyError(aRv);
|
||||
mSession->DoSessionEndTask(aRv);
|
||||
}
|
||||
|
||||
if (mDestroyRunnable &&
|
||||
|
@ -275,11 +275,18 @@ class MediaRecorder::Session: public PrincipalChangeObserver<MediaStreamTrack>,
|
|||
NS_IMETHOD
|
||||
Run() override
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mSession->MaybeCreateMutableBlobStorage();
|
||||
for (uint32_t i = 0; i < mBuffer.Length(); i++) {
|
||||
if (!mBuffer[i].IsEmpty()) {
|
||||
mSession->mMutableBlobStorage->Append(mBuffer[i].Elements(),
|
||||
mBuffer[i].Length());
|
||||
if (mBuffer[i].IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nsresult rv = mSession->mMutableBlobStorage->Append(mBuffer[i].Elements(),
|
||||
mBuffer[i].Length());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
mSession->DoSessionEndTask(rv);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -318,10 +325,9 @@ class MediaRecorder::Session: public PrincipalChangeObserver<MediaStreamTrack>,
|
|||
class DispatchStartEventRunnable : public Runnable
|
||||
{
|
||||
public:
|
||||
DispatchStartEventRunnable(Session* aSession, const nsAString& aEventName)
|
||||
explicit DispatchStartEventRunnable(Session* aSession)
|
||||
: Runnable("dom::MediaRecorder::Session::DispatchStartEventRunnable")
|
||||
, mSession(aSession)
|
||||
, mEventName(aEventName)
|
||||
{ }
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
|
@ -332,15 +338,13 @@ class MediaRecorder::Session: public PrincipalChangeObserver<MediaStreamTrack>,
|
|||
NS_ENSURE_TRUE(mSession->mRecorder, NS_OK);
|
||||
RefPtr<MediaRecorder> recorder = mSession->mRecorder;
|
||||
|
||||
recorder->SetMimeType(mSession->mMimeType);
|
||||
recorder->DispatchSimpleEvent(mEventName);
|
||||
recorder->DispatchSimpleEvent(NS_LITERAL_STRING("start"));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<Session> mSession;
|
||||
nsString mEventName;
|
||||
};
|
||||
|
||||
// To ensure that MediaRecorder has tracks to record.
|
||||
|
@ -376,8 +380,8 @@ class MediaRecorder::Session: public PrincipalChangeObserver<MediaStreamTrack>,
|
|||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
LOG(LogLevel::Debug, ("Session.DestroyRunnable session refcnt = (%d) stopIssued %d s=(%p)",
|
||||
(int)mSession->mRefCnt, mSession->mStopIssued, mSession.get()));
|
||||
LOG(LogLevel::Debug, ("Session.DestroyRunnable session refcnt = (%d) s=(%p)",
|
||||
static_cast<int>(mSession->mRefCnt), mSession.get()));
|
||||
MOZ_ASSERT(NS_IsMainThread() && mSession);
|
||||
RefPtr<MediaRecorder> recorder = mSession->mRecorder;
|
||||
if (!recorder) {
|
||||
|
@ -389,7 +393,9 @@ class MediaRecorder::Session: public PrincipalChangeObserver<MediaStreamTrack>,
|
|||
// MediaRecorder is not associated with this Session anymore, then, it's
|
||||
// safe to delete this Session.
|
||||
// Also avoid to run if this session already call stop before
|
||||
if (!mSession->mStopIssued) {
|
||||
if (mSession->mRunningState.isOk() &&
|
||||
mSession->mRunningState.unwrap() != RunningState::Stopping &&
|
||||
mSession->mRunningState.unwrap() != RunningState::Stopped) {
|
||||
recorder->StopForSessionDestruction();
|
||||
if (NS_FAILED(NS_DispatchToMainThread(new DestroyRunnable(mSession.forget())))) {
|
||||
MOZ_ASSERT(false, "NS_DispatchToMainThread failed");
|
||||
|
@ -397,6 +403,10 @@ class MediaRecorder::Session: public PrincipalChangeObserver<MediaStreamTrack>,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
if (mSession->mRunningState.isOk()) {
|
||||
mSession->mRunningState = RunningState::Stopped;
|
||||
}
|
||||
|
||||
// Dispatch stop event and clear MIME type.
|
||||
mSession->mMimeType = NS_LITERAL_STRING("");
|
||||
recorder->SetMimeType(mSession->mMimeType);
|
||||
|
@ -484,9 +494,7 @@ public:
|
|||
Session(MediaRecorder* aRecorder, int32_t aTimeSlice)
|
||||
: mRecorder(aRecorder)
|
||||
, mTimeSlice(aTimeSlice)
|
||||
, mStopIssued(false)
|
||||
, mIsStartEventFired(false)
|
||||
, mNeedSessionEndTask(true)
|
||||
, mRunningState(RunningState::Idling)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
|
@ -563,16 +571,20 @@ public:
|
|||
{
|
||||
LOG(LogLevel::Debug, ("Session.Stop %p", this));
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mStopIssued = true;
|
||||
|
||||
if (mEncoder) {
|
||||
mEncoder->Stop();
|
||||
}
|
||||
|
||||
if (mNeedSessionEndTask) {
|
||||
LOG(LogLevel::Debug, ("Session.Stop mNeedSessionEndTask %p", this));
|
||||
if (mRunningState.isOk() &&
|
||||
mRunningState.unwrap() == RunningState::Idling) {
|
||||
LOG(LogLevel::Debug, ("Session.Stop Explicit end task %p", this));
|
||||
// End the Session directly if there is no ExtractRunnable.
|
||||
DoSessionEndTask(NS_OK);
|
||||
} else if (mRunningState.isOk() &&
|
||||
(mRunningState.unwrap() == RunningState::Starting ||
|
||||
mRunningState.unwrap() == RunningState::Running)) {
|
||||
mRunningState = RunningState::Stopping;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -714,7 +726,7 @@ private:
|
|||
void MediaStreamReady(DOMMediaStream* aStream) {
|
||||
MOZ_RELEASE_ASSERT(aStream);
|
||||
|
||||
if (mStopIssued) {
|
||||
if (!mRunningState.isOk() || mRunningState.unwrap() != RunningState::Idling) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -832,6 +844,11 @@ private:
|
|||
LOG(LogLevel::Debug, ("Session.InitEncoder %p", this));
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!mRunningState.isOk() || mRunningState.unwrap() != RunningState::Idling) {
|
||||
MOZ_ASSERT_UNREACHABLE("Double-init");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a TaskQueue to read encode media data from MediaEncoder.
|
||||
MOZ_RELEASE_ASSERT(!mEncoderThread);
|
||||
RefPtr<SharedThreadPool> pool =
|
||||
|
@ -944,19 +961,36 @@ private:
|
|||
mEncoder->ConnectMediaStreamTrack(track);
|
||||
}
|
||||
|
||||
// Set mNeedSessionEndTask to false because the
|
||||
// ExtractRunnable/DestroyRunnable will take the response to
|
||||
// end the session.
|
||||
mNeedSessionEndTask = false;
|
||||
// Set mRunningState to Running so that ExtractRunnable/DestroyRunnable will
|
||||
// take the responsibility to end the session.
|
||||
mRunningState = RunningState::Starting;
|
||||
}
|
||||
|
||||
// application should get blob and onstop event
|
||||
void DoSessionEndTask(nsresult rv)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!mIsStartEventFired) {
|
||||
NS_DispatchToMainThread(
|
||||
new DispatchStartEventRunnable(this, NS_LITERAL_STRING("start")));
|
||||
if (mRunningState.isErr()) {
|
||||
// We have already ended with an error.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mRunningState.isOk() &&
|
||||
mRunningState.unwrap() == RunningState::Stopped) {
|
||||
// We have already ended gracefully.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mRunningState.isOk() &&
|
||||
(mRunningState.unwrap() == RunningState::Idling ||
|
||||
mRunningState.unwrap() == RunningState::Starting)) {
|
||||
NS_DispatchToMainThread(new DispatchStartEventRunnable(this));
|
||||
}
|
||||
|
||||
if (rv == NS_OK) {
|
||||
mRunningState = RunningState::Stopped;
|
||||
} else {
|
||||
mRunningState = Err(rv);
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
|
@ -980,8 +1014,6 @@ private:
|
|||
MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
|
||||
}
|
||||
}
|
||||
|
||||
mNeedSessionEndTask = false;
|
||||
}
|
||||
|
||||
void MediaEncoderInitialized()
|
||||
|
@ -990,7 +1022,9 @@ private:
|
|||
|
||||
// Pull encoded metadata from MediaEncoder
|
||||
nsTArray<nsTArray<uint8_t> > encodedBuf;
|
||||
nsresult rv = mEncoder->GetEncodedMetadata(&encodedBuf, mMimeType);
|
||||
nsString mime;
|
||||
nsresult rv = mEncoder->GetEncodedMetadata(&encodedBuf, mime);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
|
@ -999,18 +1033,37 @@ private:
|
|||
// Append pulled data into cache buffer.
|
||||
NS_DispatchToMainThread(new StoreEncodedBufferRunnable(this,
|
||||
Move(encodedBuf)));
|
||||
|
||||
RefPtr<Session> self = this;
|
||||
NS_DispatchToMainThread(NewRunnableFrom([self, mime]() {
|
||||
if (!self->mRecorder) {
|
||||
MOZ_ASSERT_UNREACHABLE("Recorder should be live");
|
||||
return NS_OK;
|
||||
}
|
||||
if (self->mRunningState.isOk()) {
|
||||
auto state = self->mRunningState.unwrap();
|
||||
if (state == RunningState::Starting || state == RunningState::Stopping) {
|
||||
if (state == RunningState::Starting) {
|
||||
// We set it to Running in the runnable since we can only assign
|
||||
// mRunningState on main thread. We set it before running the start
|
||||
// event runnable since that dispatches synchronously (and may cause
|
||||
// js calls to methods depending on mRunningState).
|
||||
self->mRunningState = RunningState::Running;
|
||||
}
|
||||
self->mMimeType = mime;
|
||||
self->mRecorder->SetMimeType(self->mMimeType);
|
||||
auto startEvent = MakeRefPtr<DispatchStartEventRunnable>(self);
|
||||
startEvent->Run();
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}));
|
||||
}
|
||||
|
||||
void MediaEncoderDataAvailable()
|
||||
{
|
||||
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
|
||||
|
||||
if (!mIsStartEventFired) {
|
||||
NS_DispatchToMainThread(
|
||||
new DispatchStartEventRunnable(this, NS_LITERAL_STRING("start")));
|
||||
mIsStartEventFired = true;
|
||||
}
|
||||
|
||||
Extract(false, nullptr);
|
||||
}
|
||||
|
||||
|
@ -1117,6 +1170,14 @@ private:
|
|||
}
|
||||
|
||||
private:
|
||||
enum class RunningState {
|
||||
Idling, // Session has been created
|
||||
Starting, // MediaEncoder started, waiting for data
|
||||
Running, // MediaEncoder has produced data
|
||||
Stopping, // Stop() has been called
|
||||
Stopped, // Session has stopped without any error
|
||||
};
|
||||
|
||||
// Hold reference to MediaRecorder that ensure MediaRecorder is alive
|
||||
// if there is an active session. Access ONLY on main thread.
|
||||
RefPtr<MediaRecorder> mRecorder;
|
||||
|
@ -1149,14 +1210,10 @@ private:
|
|||
// push encoded data to onDataAvailable, instead, it passive wait the client
|
||||
// side pull encoded data by calling requestData API.
|
||||
const int32_t mTimeSlice;
|
||||
// Indicate this session's stop has been called.
|
||||
bool mStopIssued;
|
||||
// Indicate the session had fire start event. Encoding thread only.
|
||||
bool mIsStartEventFired;
|
||||
// False if the InitEncoder called successfully, ensure the
|
||||
// ExtractRunnable/DestroyRunnable will end the session.
|
||||
// The session's current main thread state. The error type gets setwhen ending
|
||||
// a recording with an error. An NS_OK error is invalid.
|
||||
// Main thread only.
|
||||
bool mNeedSessionEndTask;
|
||||
Result<RunningState, nsresult> mRunningState;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED0(MediaRecorder::Session::PushBlobRunnable, Runnable)
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "mozilla/dom/HTMLInputElement.h"
|
||||
#include "mozilla/dom/HTMLLinkElement.h"
|
||||
#include "mozilla/dom/HTMLObjectElement.h"
|
||||
#include "mozilla/dom/HTMLOptionElement.h"
|
||||
#include "mozilla/dom/HTMLSharedElement.h"
|
||||
#include "mozilla/dom/HTMLTextAreaElement.h"
|
||||
#include "mozilla/dom/TabParent.h"
|
||||
|
@ -29,7 +30,6 @@
|
|||
#include "nsIDOMHTMLDocument.h"
|
||||
#include "nsIDOMHTMLInputElement.h"
|
||||
#include "nsIDOMHTMLMediaElement.h"
|
||||
#include "nsIDOMHTMLOptionElement.h"
|
||||
#include "nsIDOMHTMLScriptElement.h"
|
||||
#include "nsIDOMMozNamedAttrMap.h"
|
||||
#include "nsIDOMNode.h"
|
||||
|
@ -1166,14 +1166,15 @@ PersistNodeFixup::FixupNode(nsIDOMNode *aNodeIn,
|
|||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDOMHTMLOptionElement> nodeAsOption = do_QueryInterface(aNodeIn);
|
||||
dom::HTMLOptionElement* nodeAsOption = dom::HTMLOptionElement::FromContent(content);
|
||||
if (nodeAsOption) {
|
||||
rv = GetNodeToFixup(aNodeIn, aNodeOut);
|
||||
if (NS_SUCCEEDED(rv) && *aNodeOut) {
|
||||
nsCOMPtr<nsIDOMHTMLOptionElement> outElt = do_QueryInterface(*aNodeOut);
|
||||
bool selected;
|
||||
nodeAsOption->GetSelected(&selected);
|
||||
outElt->SetDefaultSelected(selected);
|
||||
nsCOMPtr<nsIContent> outContent = do_QueryInterface(*aNodeOut);
|
||||
dom::HTMLOptionElement* outElt = dom::HTMLOptionElement::FromContent(outContent);
|
||||
bool selected = nodeAsOption->Selected();
|
||||
IgnoredErrorResult ignored;
|
||||
outElt->SetDefaultSelected(selected, ignored);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
|
|
@ -13,19 +13,18 @@
|
|||
|
||||
[HTMLConstructor, NamedConstructor=Option(optional DOMString text = "", optional DOMString value, optional boolean defaultSelected = false, optional boolean selected = false)]
|
||||
interface HTMLOptionElement : HTMLElement {
|
||||
[CEReactions, SetterThrows]
|
||||
attribute boolean disabled;
|
||||
[CEReactions, SetterThrows]
|
||||
attribute boolean disabled;
|
||||
readonly attribute HTMLFormElement? form;
|
||||
[CEReactions, SetterThrows]
|
||||
attribute DOMString label;
|
||||
[CEReactions, SetterThrows]
|
||||
attribute boolean defaultSelected;
|
||||
[SetterThrows]
|
||||
attribute boolean selected;
|
||||
[CEReactions, SetterThrows]
|
||||
attribute DOMString value;
|
||||
[CEReactions, SetterThrows]
|
||||
attribute DOMString label;
|
||||
[CEReactions, SetterThrows]
|
||||
attribute boolean defaultSelected;
|
||||
attribute boolean selected;
|
||||
[CEReactions, SetterThrows]
|
||||
attribute DOMString value;
|
||||
|
||||
[CEReactions, SetterThrows]
|
||||
attribute DOMString text;
|
||||
[CEReactions, SetterThrows]
|
||||
attribute DOMString text;
|
||||
readonly attribute long index;
|
||||
};
|
||||
|
|
|
@ -32,17 +32,14 @@ namespace mozilla {
|
|||
|
||||
using namespace dom;
|
||||
|
||||
CreateElementTransaction::CreateElementTransaction(EditorBase& aEditorBase,
|
||||
nsAtom& aTag,
|
||||
nsINode& aParent,
|
||||
int32_t aOffsetInParent,
|
||||
nsIContent* aChildAtOffset)
|
||||
CreateElementTransaction::CreateElementTransaction(
|
||||
EditorBase& aEditorBase,
|
||||
nsAtom& aTag,
|
||||
const EditorRawDOMPoint& aPointToInsert)
|
||||
: EditTransactionBase()
|
||||
, mEditorBase(&aEditorBase)
|
||||
, mTag(&aTag)
|
||||
, mParent(&aParent)
|
||||
, mOffsetInParent(aOffsetInParent)
|
||||
, mRefNode(aChildAtOffset)
|
||||
, mPointToInsert(aPointToInsert)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -53,9 +50,8 @@ CreateElementTransaction::~CreateElementTransaction()
|
|||
NS_IMPL_CYCLE_COLLECTION_INHERITED(CreateElementTransaction,
|
||||
EditTransactionBase,
|
||||
mEditorBase,
|
||||
mParent,
|
||||
mNewNode,
|
||||
mRefNode)
|
||||
mPointToInsert,
|
||||
mNewNode)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(CreateElementTransaction, EditTransactionBase)
|
||||
NS_IMPL_RELEASE_INHERITED(CreateElementTransaction, EditTransactionBase)
|
||||
|
@ -66,7 +62,8 @@ NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
|
|||
NS_IMETHODIMP
|
||||
CreateElementTransaction::DoTransaction()
|
||||
{
|
||||
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTag) || NS_WARN_IF(!mParent)) {
|
||||
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTag) ||
|
||||
NS_WARN_IF(!mPointToInsert.IsSet())) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
|
@ -77,24 +74,12 @@ CreateElementTransaction::DoTransaction()
|
|||
mEditorBase->MarkNodeDirty(GetAsDOMNode(mNewNode));
|
||||
|
||||
// Insert the new node
|
||||
ErrorResult rv;
|
||||
if (mOffsetInParent == -1) {
|
||||
mParent->AppendChild(*mNewNode, rv);
|
||||
return rv.StealNSResult();
|
||||
ErrorResult error;
|
||||
InsertNewNode(error);
|
||||
if (NS_WARN_IF(error.Failed())) {
|
||||
return error.StealNSResult();
|
||||
}
|
||||
|
||||
mOffsetInParent = std::min(mOffsetInParent,
|
||||
static_cast<int32_t>(mParent->GetChildCount()));
|
||||
|
||||
if (!mRefNode) {
|
||||
// Note, it's ok for mRefNode to be null. That means append
|
||||
mRefNode = mParent->GetChildAt(mOffsetInParent);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIContent> refNode = mRefNode;
|
||||
mParent->InsertBefore(*mNewNode, refNode, rv);
|
||||
NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
|
||||
|
||||
// Only set selection to insertion point if editor gives permission
|
||||
if (!mEditorBase->GetShouldTxnSetSelection()) {
|
||||
// Do nothing - DOM range gravity will adjust selection
|
||||
|
@ -111,41 +96,77 @@ CreateElementTransaction::DoTransaction()
|
|||
// in this method?
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
rv = selection->Collapse(afterNewNode);
|
||||
NS_ASSERTION(!rv.Failed(),
|
||||
"selection could not be collapsed after insert");
|
||||
selection->Collapse(afterNewNode, error);
|
||||
if (error.Failed()) {
|
||||
NS_WARNING("selection could not be collapsed after insert");
|
||||
error.SuppressException();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
CreateElementTransaction::InsertNewNode(ErrorResult& aError)
|
||||
{
|
||||
if (mPointToInsert.IsSetAndValid()) {
|
||||
if (mPointToInsert.IsEndOfContainer()) {
|
||||
mPointToInsert.Container()->AppendChild(*mNewNode, aError);
|
||||
NS_WARNING_ASSERTION(!aError.Failed(), "Failed to append the new node");
|
||||
return;
|
||||
}
|
||||
mPointToInsert.Container()->InsertBefore(*mNewNode,
|
||||
mPointToInsert.GetChildAtOffset(),
|
||||
aError);
|
||||
NS_WARNING_ASSERTION(!aError.Failed(), "Failed to insert the new node");
|
||||
return;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(mPointToInsert.GetChildAtOffset() &&
|
||||
mPointToInsert.Container() !=
|
||||
mPointToInsert.GetChildAtOffset()->GetParentNode())) {
|
||||
aError.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
// If mPointToInsert has only offset and it's not valid, we need to treat
|
||||
// it as pointing end of the container.
|
||||
mPointToInsert.Container()->AppendChild(*mNewNode, aError);
|
||||
NS_WARNING_ASSERTION(!aError.Failed(), "Failed to append the new node");
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CreateElementTransaction::UndoTransaction()
|
||||
{
|
||||
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mParent)) {
|
||||
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mPointToInsert.IsSet())) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
ErrorResult rv;
|
||||
mParent->RemoveChild(*mNewNode, rv);
|
||||
|
||||
return rv.StealNSResult();
|
||||
ErrorResult error;
|
||||
mPointToInsert.Container()->RemoveChild(*mNewNode, error);
|
||||
if (NS_WARN_IF(error.Failed())) {
|
||||
return error.StealNSResult();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CreateElementTransaction::RedoTransaction()
|
||||
{
|
||||
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mParent)) {
|
||||
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mPointToInsert.IsSet())) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// First, reset mNewNode so it has no attributes or content
|
||||
// XXX We never actually did this, we only cleared mNewNode's contents if it
|
||||
// was a CharacterData node (which it's not, it's an Element)
|
||||
// XXX Don't we need to set selection like DoTransaction()?
|
||||
|
||||
// Now, reinsert mNewNode
|
||||
ErrorResult rv;
|
||||
nsCOMPtr<nsIContent> refNode = mRefNode;
|
||||
mParent->InsertBefore(*mNewNode, refNode, rv);
|
||||
return rv.StealNSResult();
|
||||
ErrorResult error;
|
||||
InsertNewNode(error);
|
||||
if (NS_WARN_IF(error.Failed())) {
|
||||
return error.StealNSResult();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#ifndef CreateElementTransaction_h
|
||||
#define CreateElementTransaction_h
|
||||
|
||||
#include "mozilla/EditorDOMPoint.h"
|
||||
#include "mozilla/EditTransactionBase.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
|
@ -32,17 +33,14 @@ public:
|
|||
* Initialize the transaction.
|
||||
* @param aEditorBase The provider of basic editing functionality.
|
||||
* @param aTag The tag (P, HR, TABLE, etc.) for the new element.
|
||||
* @param aParent The node into which the new element will be
|
||||
* inserted.
|
||||
* @param aOffsetInParent The location in aParent to insert the new element.
|
||||
* If eAppend, the new element is appended as the last
|
||||
* child.
|
||||
* @param aPointToInsert The new node will be inserted before the child at
|
||||
* aPointToInsert. If this refers end of the container
|
||||
* or after, the new node will be appended to the
|
||||
* container.
|
||||
*/
|
||||
CreateElementTransaction(EditorBase& aEditorBase,
|
||||
nsAtom& aTag,
|
||||
nsINode& aParent,
|
||||
int32_t aOffsetInParent,
|
||||
nsIContent* aChildAtOffset);
|
||||
const EditorRawDOMPoint& aPointToInsert);
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CreateElementTransaction,
|
||||
|
@ -57,24 +55,22 @@ public:
|
|||
protected:
|
||||
virtual ~CreateElementTransaction();
|
||||
|
||||
/**
|
||||
* InsertNewNode() inserts mNewNode before the child node at mPointToInsert.
|
||||
*/
|
||||
void InsertNewNode(ErrorResult& aError);
|
||||
|
||||
// The document into which the new node will be inserted.
|
||||
RefPtr<EditorBase> mEditorBase;
|
||||
|
||||
// The tag (mapping to object type) for the new element.
|
||||
RefPtr<nsAtom> mTag;
|
||||
|
||||
// The node into which the new node will be inserted.
|
||||
nsCOMPtr<nsINode> mParent;
|
||||
|
||||
// The index in mParent for the new node.
|
||||
int32_t mOffsetInParent;
|
||||
// The DOM point we will insert mNewNode.
|
||||
RangeBoundary mPointToInsert;
|
||||
|
||||
// The new node to insert.
|
||||
nsCOMPtr<dom::Element> mNewNode;
|
||||
|
||||
// The node we will insert mNewNode before. We compute this ourselves if it
|
||||
// is not set by the constructor.
|
||||
nsCOMPtr<nsIContent> mRefNode;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -1419,11 +1419,15 @@ EditorBase::SetSpellcheckUserOverride(bool enable)
|
|||
|
||||
already_AddRefed<Element>
|
||||
EditorBase::CreateNode(nsAtom* aTag,
|
||||
nsINode* aParent,
|
||||
int32_t aPosition,
|
||||
nsIContent* aChildAtPosition)
|
||||
EditorRawDOMPoint& aPointToInsert)
|
||||
{
|
||||
MOZ_ASSERT(aTag && aParent);
|
||||
MOZ_ASSERT(aTag);
|
||||
MOZ_ASSERT(aPointToInsert.IsSetAndValid());
|
||||
|
||||
// XXX We need to offset at new node to mRangeUpdater. Therefore, we need
|
||||
// to compute the offset now but this is expensive. So, if it's possible,
|
||||
// we need to redesign mRangeUpdater as avoiding using indices.
|
||||
int32_t offset = static_cast<int32_t>(aPointToInsert.Offset());
|
||||
|
||||
AutoRules beginRulesSniffing(this, EditAction::createNode, nsIEditor::eNext);
|
||||
|
||||
|
@ -1431,28 +1435,31 @@ EditorBase::CreateNode(nsAtom* aTag,
|
|||
AutoActionListenerArray listeners(mActionListeners);
|
||||
for (auto& listener : listeners) {
|
||||
listener->WillCreateNode(nsDependentAtomString(aTag),
|
||||
GetAsDOMNode(aParent), aPosition);
|
||||
GetAsDOMNode(aPointToInsert.GetChildAtOffset()));
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<Element> ret;
|
||||
|
||||
RefPtr<CreateElementTransaction> transaction =
|
||||
CreateTxnForCreateElement(*aTag, *aParent, aPosition,
|
||||
aChildAtPosition);
|
||||
CreateTxnForCreateElement(*aTag, aPointToInsert);
|
||||
nsresult rv = DoTransaction(transaction);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
ret = transaction->GetNewNode();
|
||||
MOZ_ASSERT(ret);
|
||||
// Now, aPointToInsert may be invalid. I.e., ChildAtOffset() keeps
|
||||
// referring the next sibling of new node but Offset() refers the
|
||||
// new node. Let's make refer the new node.
|
||||
aPointToInsert.Set(ret);
|
||||
}
|
||||
|
||||
mRangeUpdater.SelAdjCreateNode(aParent, aPosition);
|
||||
mRangeUpdater.SelAdjCreateNode(aPointToInsert.Container(), offset);
|
||||
|
||||
{
|
||||
AutoActionListenerArray listeners(mActionListeners);
|
||||
for (auto& listener : listeners) {
|
||||
listener->DidCreateNode(nsDependentAtomString(aTag), GetAsDOMNode(ret),
|
||||
GetAsDOMNode(aParent), aPosition, rv);
|
||||
listener->DidCreateNode(nsDependentAtomString(aTag),
|
||||
GetAsDOMNode(ret), rv);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4214,16 +4221,29 @@ EditorBase::DeleteSelectionAndCreateElement(nsAtom& aTag)
|
|||
RefPtr<Selection> selection = GetSelection();
|
||||
NS_ENSURE_TRUE(selection, nullptr);
|
||||
|
||||
nsCOMPtr<nsINode> node = selection->GetAnchorNode();
|
||||
uint32_t offset = selection->AnchorOffset();
|
||||
nsIContent* child = selection->GetChildAtAnchorOffset();
|
||||
|
||||
nsCOMPtr<Element> newElement = CreateNode(&aTag, node, offset, child);
|
||||
EditorRawDOMPoint pointToInsert(selection->GetChildAtAnchorOffset());
|
||||
if (!pointToInsert.IsSet()) {
|
||||
// Perhaps, the anchor point is in a text node.
|
||||
pointToInsert.Set(selection->GetAnchorNode(), selection->AnchorOffset());
|
||||
if (NS_WARN_IF(!pointToInsert.IsSet())) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
RefPtr<Element> newElement = CreateNode(&aTag, pointToInsert);
|
||||
|
||||
// We want the selection to be just after the new node
|
||||
rv = selection->Collapse(node, offset + 1);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
|
||||
NS_WARNING_ASSERTION(advanced,
|
||||
"Failed to move offset next to the new element");
|
||||
ErrorResult error;
|
||||
selection->Collapse(pointToInsert, error);
|
||||
if (NS_WARN_IF(error.Failed())) {
|
||||
// XXX Even if it succeeded to create new element, this returns error
|
||||
// when Selection.Collapse() fails something. This could occur with
|
||||
// mutation observer or mutation event listener.
|
||||
error.SuppressException();
|
||||
return nullptr;
|
||||
}
|
||||
return newElement.forget();
|
||||
}
|
||||
|
||||
|
@ -4368,13 +4388,10 @@ EditorBase::CreateTxnForRemoveAttribute(Element& aElement,
|
|||
|
||||
already_AddRefed<CreateElementTransaction>
|
||||
EditorBase::CreateTxnForCreateElement(nsAtom& aTag,
|
||||
nsINode& aParent,
|
||||
int32_t aPosition,
|
||||
nsIContent* aChildAtPosition)
|
||||
const EditorRawDOMPoint& aPointToInsert)
|
||||
{
|
||||
RefPtr<CreateElementTransaction> transaction =
|
||||
new CreateElementTransaction(*this, aTag, aParent, aPosition,
|
||||
aChildAtPosition);
|
||||
new CreateElementTransaction(*this, aTag, aPointToInsert);
|
||||
|
||||
return transaction.forget();
|
||||
}
|
||||
|
|
|
@ -394,17 +394,39 @@ protected:
|
|||
CreateTxnForRemoveAttribute(Element& aElement, nsAtom& aAttribute);
|
||||
|
||||
/**
|
||||
* Create a transaction for creating a new child node of aParent of type aTag.
|
||||
* Create a transaction for creating a new child node of the container of
|
||||
* aPointToInsert of type aTag.
|
||||
*
|
||||
* @param aTag The element name to create.
|
||||
* @param aPointToInsert The insertion point of new element. If this refers
|
||||
* end of the container or after, the transaction
|
||||
* will append the element to the container.
|
||||
* Otherwise, will insert the element before the
|
||||
* child node referred by this.
|
||||
* @return A CreateElementTransaction which are initialized
|
||||
* with the arguments.
|
||||
*/
|
||||
already_AddRefed<CreateElementTransaction>
|
||||
CreateTxnForCreateElement(nsAtom& aTag,
|
||||
nsINode& aParent,
|
||||
int32_t aPosition,
|
||||
nsIContent* aChildAtPosition);
|
||||
const EditorRawDOMPoint& aPointToInsert);
|
||||
|
||||
already_AddRefed<Element> CreateNode(nsAtom* aTag, nsINode* aParent,
|
||||
int32_t aPosition,
|
||||
nsIContent* aChildAtPosition = nullptr);
|
||||
/**
|
||||
* Create an element node whose name is aTag at before aPointToInsert. When
|
||||
* this succeed to create an element node, this sets aPointToInsert to the
|
||||
* new element because the relation of child and offset may be broken.
|
||||
* If the caller needs to collapse the selection to next to the new element
|
||||
* node, it should call |aPointToInsert.AdvanceOffset()| after calling this.
|
||||
*
|
||||
* @param aTag The element name to create.
|
||||
* @param aPointToInsert The insertion point of new element. If this refers
|
||||
* end of the container or after, the transaction
|
||||
* will append the element to the container.
|
||||
* Otherwise, will insert the element before the
|
||||
* child node referred by this.
|
||||
* @return The created new element node.
|
||||
*/
|
||||
already_AddRefed<Element> CreateNode(nsAtom* aTag,
|
||||
EditorRawDOMPoint& aPointToInsert);
|
||||
|
||||
/**
|
||||
* Create a transaction for inserting aNode as a child of aParent.
|
||||
|
|
|
@ -59,15 +59,16 @@ public:
|
|||
aPointedNode && aPointedNode->IsContent() ?
|
||||
aPointedNode->GetParentNode() : nullptr,
|
||||
aPointedNode && aPointedNode->IsContent() ?
|
||||
GetRef(aPointedNode->AsContent()) : nullptr)
|
||||
GetRef(aPointedNode->GetParentNode(),
|
||||
aPointedNode->AsContent()) : nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
EditorDOMPointBase(nsINode* aConatiner,
|
||||
EditorDOMPointBase(nsINode* aContainer,
|
||||
nsIContent* aPointedNode,
|
||||
int32_t aOffset)
|
||||
: RangeBoundaryBase<ParentType, RefType>(aConatiner,
|
||||
GetRef(aPointedNode),
|
||||
: RangeBoundaryBase<ParentType, RefType>(aContainer,
|
||||
GetRef(aContainer, aPointedNode),
|
||||
aOffset)
|
||||
{
|
||||
}
|
||||
|
@ -84,10 +85,18 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
static nsIContent* GetRef(nsIContent* aPointedNode)
|
||||
static nsIContent* GetRef(nsINode* aContainerNode, nsIContent* aPointedNode)
|
||||
{
|
||||
MOZ_ASSERT(aPointedNode);
|
||||
return aPointedNode ? aPointedNode->GetPreviousSibling() : nullptr;
|
||||
// If referring one of a child of the container, the 'ref' should be the
|
||||
// previous sibling of the referring child.
|
||||
if (aPointedNode) {
|
||||
return aPointedNode->GetPreviousSibling();
|
||||
}
|
||||
// If referring after the last child, the 'ref' should be the last child.
|
||||
if (aContainerNode && aContainerNode->IsContainerNode()) {
|
||||
return aContainerNode->GetLastChild();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -3396,23 +3396,29 @@ HTMLEditRules::WillMakeList(Selection* aSelection,
|
|||
address_of(child));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
nsCOMPtr<Element> theList =
|
||||
mHTMLEditor->CreateNode(listType, container, offset, child);
|
||||
EditorRawDOMPoint atListItemToInsertBefore(container, child, offset);
|
||||
RefPtr<Element> theList =
|
||||
mHTMLEditor->CreateNode(listType, atListItemToInsertBefore);
|
||||
NS_ENSURE_STATE(theList);
|
||||
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
nsCOMPtr<Element> theListItem =
|
||||
mHTMLEditor->CreateNode(itemType, theList, 0, theList->GetFirstChild());
|
||||
EditorRawDOMPoint atFirstListItemToInsertBefore(theList, 0);
|
||||
RefPtr<Element> theListItem =
|
||||
mHTMLEditor->CreateNode(itemType, atFirstListItemToInsertBefore);
|
||||
NS_ENSURE_STATE(theListItem);
|
||||
|
||||
// remember our new block for postprocessing
|
||||
mNewBlock = theListItem;
|
||||
// put selection in new list item
|
||||
*aHandled = true;
|
||||
rv = aSelection->Collapse(theListItem, 0);
|
||||
ErrorResult error;
|
||||
aSelection->Collapse(EditorRawDOMPoint(theListItem, 0), error);
|
||||
// Don't restore the selection
|
||||
selectionRestorer.Abort();
|
||||
return rv;
|
||||
if (NS_WARN_IF(!error.Failed())) {
|
||||
return error.StealNSResult();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// if there is only one node in the array, and it is a list, div, or
|
||||
|
@ -3498,13 +3504,9 @@ HTMLEditRules::WillMakeList(Selection* aSelection,
|
|||
mHTMLEditor->SplitNode(*curParent->AsContent(), offset, rv);
|
||||
NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
|
||||
newBlock = splitNode ? splitNode->AsElement() : nullptr;
|
||||
int32_t offset;
|
||||
nsCOMPtr<nsINode> parent = EditorBase::GetNodeLocation(curParent,
|
||||
&offset);
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
curList = mHTMLEditor->CreateNode(listType, parent, offset,
|
||||
curParent ? curParent->AsContent()
|
||||
: nullptr);
|
||||
EditorRawDOMPoint atCurParent(curParent);
|
||||
curList = mHTMLEditor->CreateNode(listType, atCurParent);
|
||||
NS_ENSURE_STATE(curList);
|
||||
}
|
||||
// move list item to new list
|
||||
|
@ -3575,8 +3577,8 @@ HTMLEditRules::WillMakeList(Selection* aSelection,
|
|||
address_of(curChild));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
curList = mHTMLEditor->CreateNode(listType, curParent, offset,
|
||||
curChild);
|
||||
EditorRawDOMPoint atCurChild(curParent, curChild, offset);
|
||||
curList = mHTMLEditor->CreateNode(listType, atCurChild);
|
||||
// remember our new block for postprocessing
|
||||
mNewBlock = curList;
|
||||
// curList is now the correct thing to put curNode in
|
||||
|
@ -3781,13 +3783,15 @@ HTMLEditRules::MakeBasicBlock(Selection& aSelection, nsAtom& blockType)
|
|||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
// We don't need to act on this node any more
|
||||
arrayOfNodes.RemoveElement(brNode);
|
||||
child = nullptr;
|
||||
// XXX We need to recompute child here because SplitAsNeeded() and
|
||||
// EditorBase::SplitNodeDeep() don't compute child in some cases.
|
||||
child = container->GetChildAt(offset);
|
||||
}
|
||||
// Make sure we can put a block here
|
||||
rv = SplitAsNeeded(blockType, container, offset, address_of(child));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<Element> block =
|
||||
htmlEditor->CreateNode(&blockType, container, offset, child);
|
||||
EditorRawDOMPoint atChild(container, child, offset);
|
||||
RefPtr<Element> block = htmlEditor->CreateNode(&blockType, atChild);
|
||||
NS_ENSURE_STATE(block);
|
||||
// Remember our new block for postprocessing
|
||||
mNewBlock = block;
|
||||
|
@ -3928,9 +3932,9 @@ HTMLEditRules::WillCSSIndent(Selection* aSelection,
|
|||
rv = SplitAsNeeded(*nsGkAtoms::div, container, offset, address_of(child));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
nsCOMPtr<Element> theBlock = mHTMLEditor->CreateNode(nsGkAtoms::div,
|
||||
container, offset,
|
||||
child);
|
||||
EditorRawDOMPoint atChild(container, child, offset);
|
||||
RefPtr<Element> theBlock =
|
||||
mHTMLEditor->CreateNode(nsGkAtoms::div, atChild);
|
||||
NS_ENSURE_STATE(theBlock);
|
||||
// remember our new block for postprocessing
|
||||
mNewBlock = theBlock;
|
||||
|
@ -3945,10 +3949,15 @@ HTMLEditRules::WillCSSIndent(Selection* aSelection,
|
|||
}
|
||||
// put selection in new block
|
||||
*aHandled = true;
|
||||
rv = aSelection->Collapse(theBlock, 0);
|
||||
EditorRawDOMPoint atStartOfTheBlock(theBlock, 0);
|
||||
ErrorResult error;
|
||||
aSelection->Collapse(atStartOfTheBlock, error);
|
||||
// Don't restore the selection
|
||||
selectionRestorer.Abort();
|
||||
return rv;
|
||||
if (NS_WARN_IF(!error.Failed())) {
|
||||
return error.StealNSResult();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Ok, now go through all the nodes and put them in a blockquote,
|
||||
|
@ -4025,8 +4034,9 @@ HTMLEditRules::WillCSSIndent(Selection* aSelection,
|
|||
address_of(curChild));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
EditorRawDOMPoint atCurChild(curParent, curChild, offset);
|
||||
curList = mHTMLEditor->CreateNode(curParent->NodeInfo()->NameAtom(),
|
||||
curParent, offset, curChild);
|
||||
atCurChild);
|
||||
NS_ENSURE_STATE(curList);
|
||||
// curList is now the correct thing to put curNode in
|
||||
// remember our new block for postprocessing
|
||||
|
@ -4058,8 +4068,8 @@ HTMLEditRules::WillCSSIndent(Selection* aSelection,
|
|||
address_of(curChild));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
curQuote = mHTMLEditor->CreateNode(nsGkAtoms::div, curParent,
|
||||
offset, curChild);
|
||||
EditorRawDOMPoint atCurChild(curParent, curChild, offset);
|
||||
curQuote = mHTMLEditor->CreateNode(nsGkAtoms::div, atCurChild);
|
||||
NS_ENSURE_STATE(curQuote);
|
||||
ChangeIndentation(*curQuote, Change::plus);
|
||||
// remember our new block for postprocessing
|
||||
|
@ -4127,9 +4137,9 @@ HTMLEditRules::WillHTMLIndent(Selection* aSelection,
|
|||
address_of(child));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
nsCOMPtr<Element> theBlock = mHTMLEditor->CreateNode(nsGkAtoms::blockquote,
|
||||
container, offset,
|
||||
child);
|
||||
EditorRawDOMPoint atChild(container, child, offset);
|
||||
RefPtr<Element> theBlock =
|
||||
mHTMLEditor->CreateNode(nsGkAtoms::blockquote, atChild);
|
||||
NS_ENSURE_STATE(theBlock);
|
||||
// remember our new block for postprocessing
|
||||
mNewBlock = theBlock;
|
||||
|
@ -4143,10 +4153,15 @@ HTMLEditRules::WillHTMLIndent(Selection* aSelection,
|
|||
}
|
||||
// put selection in new block
|
||||
*aHandled = true;
|
||||
rv = aSelection->Collapse(theBlock, 0);
|
||||
EditorRawDOMPoint atStartOfTheBlock(theBlock, 0);
|
||||
ErrorResult error;
|
||||
aSelection->Collapse(atStartOfTheBlock, error);
|
||||
// Don't restore the selection
|
||||
selectionRestorer.Abort();
|
||||
return rv;
|
||||
if (NS_WARN_IF(!error.Failed())) {
|
||||
return error.StealNSResult();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Ok, now go through all the nodes and put them in a blockquote,
|
||||
|
@ -4224,8 +4239,9 @@ HTMLEditRules::WillHTMLIndent(Selection* aSelection,
|
|||
address_of(curChild));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
EditorRawDOMPoint atCurChild(curParent, curChild, offset);
|
||||
curList = mHTMLEditor->CreateNode(curParent->NodeInfo()->NameAtom(),
|
||||
curParent, offset, curChild);
|
||||
atCurChild);
|
||||
NS_ENSURE_STATE(curList);
|
||||
// curList is now the correct thing to put curNode in
|
||||
// remember our new block for postprocessing
|
||||
|
@ -4269,8 +4285,9 @@ HTMLEditRules::WillHTMLIndent(Selection* aSelection,
|
|||
offset, address_of(curChild));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
EditorRawDOMPoint atCurChild(curParent, curChild, offset);
|
||||
curList = mHTMLEditor->CreateNode(curParent->NodeInfo()->NameAtom(),
|
||||
curParent, offset, curChild);
|
||||
atCurChild);
|
||||
NS_ENSURE_STATE(curList);
|
||||
}
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
|
@ -4301,8 +4318,8 @@ HTMLEditRules::WillHTMLIndent(Selection* aSelection,
|
|||
address_of(curChild));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
curQuote = mHTMLEditor->CreateNode(nsGkAtoms::blockquote, curParent,
|
||||
offset, curChild);
|
||||
EditorRawDOMPoint atCurChild(curParent, curChild, offset);
|
||||
curQuote = mHTMLEditor->CreateNode(nsGkAtoms::blockquote, atCurChild);
|
||||
NS_ENSURE_STATE(curQuote);
|
||||
// remember our new block for postprocessing
|
||||
mNewBlock = curQuote;
|
||||
|
@ -4916,8 +4933,8 @@ HTMLEditRules::WillAlign(Selection& aSelection,
|
|||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
}
|
||||
nsCOMPtr<Element> div = htmlEditor->CreateNode(nsGkAtoms::div, parent,
|
||||
offset, child);
|
||||
EditorRawDOMPoint atChild(parent, child, offset);
|
||||
RefPtr<Element> div = htmlEditor->CreateNode(nsGkAtoms::div, atChild);
|
||||
NS_ENSURE_STATE(div);
|
||||
// Remember our new block for postprocessing
|
||||
mNewBlock = div;
|
||||
|
@ -4928,10 +4945,14 @@ HTMLEditRules::WillAlign(Selection& aSelection,
|
|||
// Put in a moz-br so that it won't get deleted
|
||||
rv = CreateMozBR(div->AsDOMNode(), 0);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = aSelection.Collapse(div, 0);
|
||||
EditorRawDOMPoint atStartOfDiv(div, 0);
|
||||
ErrorResult error;
|
||||
aSelection.Collapse(atStartOfDiv, error);
|
||||
// Don't restore the selection
|
||||
selectionRestorer.Abort();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (NS_WARN_IF(error.Failed())) {
|
||||
return error.StealNSResult();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -5022,8 +5043,8 @@ HTMLEditRules::WillAlign(Selection& aSelection,
|
|||
rv = SplitAsNeeded(*nsGkAtoms::div, curParent, offset,
|
||||
address_of(curChild));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
curDiv = htmlEditor->CreateNode(nsGkAtoms::div, curParent, offset,
|
||||
curChild);
|
||||
EditorRawDOMPoint atCurChild(curParent, curChild, offset);
|
||||
curDiv = htmlEditor->CreateNode(nsGkAtoms::div, atCurChild);
|
||||
NS_ENSURE_STATE(curDiv);
|
||||
// Remember our new block for postprocessing
|
||||
mNewBlock = curDiv;
|
||||
|
@ -5099,8 +5120,9 @@ HTMLEditRules::AlignBlockContents(nsIDOMNode* aNode,
|
|||
} else {
|
||||
// else we need to put in a div, set the alignment, and toss in all the children
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
RefPtr<Element> divElem = mHTMLEditor->CreateNode(nsGkAtoms::div, node, 0,
|
||||
node->GetFirstChild());
|
||||
EditorRawDOMPoint atStartOfNode(node, 0);
|
||||
RefPtr<Element> divElem =
|
||||
mHTMLEditor->CreateNode(nsGkAtoms::div, atStartOfNode);
|
||||
NS_ENSURE_STATE(divElem);
|
||||
// set up the alignment on the div
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
|
@ -6618,10 +6640,11 @@ HTMLEditRules::ReturnInHeader(Selection& aSelection,
|
|||
// Create a paragraph
|
||||
nsAtom& paraAtom = DefaultParagraphSeparator();
|
||||
// We want a wrapper element even if we separate with <br>
|
||||
nsCOMPtr<Element> pNode =
|
||||
htmlEditor->CreateNode(¶Atom == nsGkAtoms::br ? nsGkAtoms::p
|
||||
: ¶Atom,
|
||||
headerParent, offset + 1);
|
||||
EditorRawDOMPoint nextToHeader(headerParent, offset + 1);
|
||||
RefPtr<Element> pNode =
|
||||
htmlEditor->CreateNode(¶Atom == nsGkAtoms::br ?
|
||||
nsGkAtoms::p : ¶Atom,
|
||||
nextToHeader);
|
||||
NS_ENSURE_STATE(pNode);
|
||||
|
||||
// Append a <br> to it
|
||||
|
@ -6899,10 +6922,11 @@ HTMLEditRules::ReturnInListItem(Selection& aSelection,
|
|||
// Time to insert a paragraph
|
||||
nsAtom& paraAtom = DefaultParagraphSeparator();
|
||||
// We want a wrapper even if we separate with <br>
|
||||
nsCOMPtr<Element> pNode =
|
||||
htmlEditor->CreateNode(¶Atom == nsGkAtoms::br ? nsGkAtoms::p
|
||||
: ¶Atom,
|
||||
listParent, offset + 1);
|
||||
EditorRawDOMPoint atNextListItem(listParent, offset + 1);
|
||||
RefPtr<Element> pNode =
|
||||
htmlEditor->CreateNode(¶Atom == nsGkAtoms::br ?
|
||||
nsGkAtoms::p : ¶Atom,
|
||||
atNextListItem);
|
||||
NS_ENSURE_STATE(pNode);
|
||||
|
||||
// Append a <br> to it
|
||||
|
@ -6948,9 +6972,11 @@ HTMLEditRules::ReturnInListItem(Selection& aSelection,
|
|||
|
||||
nsAtom* listAtom = nodeAtom == nsGkAtoms::dt ? nsGkAtoms::dd
|
||||
: nsGkAtoms::dt;
|
||||
nsCOMPtr<Element> newListItem =
|
||||
htmlEditor->CreateNode(listAtom, list, itemOffset + 1,
|
||||
aListItem.GetNextSibling());
|
||||
MOZ_DIAGNOSTIC_ASSERT(itemOffset != -1);
|
||||
EditorRawDOMPoint atNextListItem(list, aListItem.GetNextSibling(),
|
||||
itemOffset + 1);
|
||||
RefPtr<Element> newListItem =
|
||||
htmlEditor->CreateNode(listAtom, atNextListItem);
|
||||
NS_ENSURE_STATE(newListItem);
|
||||
rv = htmlEditor->DeleteNode(&aListItem);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
@ -7053,8 +7079,8 @@ HTMLEditRules::MakeBlockquote(nsTArray<OwningNonNull<nsINode>>& aNodeArray)
|
|||
address_of(curChild));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
curBlock = mHTMLEditor->CreateNode(nsGkAtoms::blockquote, curParent,
|
||||
offset, curChild);
|
||||
EditorRawDOMPoint atCurChild(curParent, curChild, offset);
|
||||
curBlock = mHTMLEditor->CreateNode(nsGkAtoms::blockquote, atCurChild);
|
||||
NS_ENSURE_STATE(curBlock);
|
||||
// remember our new block for postprocessing
|
||||
mNewBlock = curBlock;
|
||||
|
@ -7223,9 +7249,9 @@ HTMLEditRules::ApplyBlockStyle(nsTArray<OwningNonNull<nsINode>>& aNodeArray,
|
|||
nsresult rv = SplitAsNeeded(aBlockTag, curParent, offset,
|
||||
address_of(curChild));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<Element> theBlock =
|
||||
htmlEditor->CreateNode(&aBlockTag, curParent, offset,
|
||||
curChild);
|
||||
EditorRawDOMPoint atCurChild(curParent, curChild, offset);
|
||||
RefPtr<Element> theBlock =
|
||||
htmlEditor->CreateNode(&aBlockTag, atCurChild);
|
||||
NS_ENSURE_STATE(theBlock);
|
||||
// Remember our new block for postprocessing
|
||||
mNewBlock = theBlock;
|
||||
|
@ -7245,8 +7271,8 @@ HTMLEditRules::ApplyBlockStyle(nsTArray<OwningNonNull<nsINode>>& aNodeArray,
|
|||
nsresult rv = SplitAsNeeded(aBlockTag, curParent, offset,
|
||||
address_of(curChild));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
curBlock = htmlEditor->CreateNode(&aBlockTag, curParent, offset,
|
||||
curChild);
|
||||
EditorRawDOMPoint atCurChild(curParent, curChild, offset);
|
||||
curBlock = htmlEditor->CreateNode(&aBlockTag, atCurChild);
|
||||
NS_ENSURE_STATE(curBlock);
|
||||
// Remember our new block for postprocessing
|
||||
mNewBlock = curBlock;
|
||||
|
@ -7273,8 +7299,8 @@ HTMLEditRules::ApplyBlockStyle(nsTArray<OwningNonNull<nsINode>>& aNodeArray,
|
|||
nsresult rv = SplitAsNeeded(aBlockTag, curParent, offset,
|
||||
address_of(curChild));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
curBlock = htmlEditor->CreateNode(&aBlockTag, curParent, offset,
|
||||
curChild);
|
||||
EditorRawDOMPoint atCurChild(curParent, curChild, offset);
|
||||
curBlock = htmlEditor->CreateNode(&aBlockTag, atCurChild);
|
||||
NS_ENSURE_STATE(curBlock);
|
||||
// Remember our new block for postprocessing
|
||||
mNewBlock = curBlock;
|
||||
|
@ -8518,24 +8544,21 @@ HTMLEditRules::InsertBRIfNeededInternal(nsINode& aNode,
|
|||
|
||||
NS_IMETHODIMP
|
||||
HTMLEditRules::WillCreateNode(const nsAString& aTag,
|
||||
nsIDOMNode* aParent,
|
||||
int32_t aPosition)
|
||||
nsIDOMNode* aNextSiblingOfNewNode)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLEditRules::DidCreateNode(const nsAString& aTag,
|
||||
nsIDOMNode* aNode,
|
||||
nsIDOMNode* aParent,
|
||||
int32_t aPosition,
|
||||
nsIDOMNode* aNewNode,
|
||||
nsresult aResult)
|
||||
{
|
||||
if (!mListenerEnabled) {
|
||||
return NS_OK;
|
||||
}
|
||||
// assumption that Join keeps the righthand node
|
||||
nsresult rv = mUtilRange->SelectNode(aNode);
|
||||
nsresult rv = mUtilRange->SelectNode(aNewNode);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return UpdateDocChangeRange(mUtilRange);
|
||||
}
|
||||
|
@ -9028,8 +9051,9 @@ HTMLEditRules::WillAbsolutePosition(Selection& aSelection,
|
|||
rv = SplitAsNeeded(*nsGkAtoms::div, parent, offset,
|
||||
address_of(child));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<Element> positionedDiv =
|
||||
htmlEditor->CreateNode(nsGkAtoms::div, parent, offset, child);
|
||||
EditorRawDOMPoint atChild(parent, child, offset);
|
||||
RefPtr<Element> positionedDiv =
|
||||
htmlEditor->CreateNode(nsGkAtoms::div, atChild);
|
||||
NS_ENSURE_STATE(positionedDiv);
|
||||
// Remember our new block for postprocessing
|
||||
mNewBlock = positionedDiv;
|
||||
|
@ -9084,16 +9108,15 @@ HTMLEditRules::WillAbsolutePosition(Selection& aSelection,
|
|||
SplitAsNeeded(*curParent->NodeInfo()->NameAtom(), curParent, offset);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!curPositionedDiv) {
|
||||
nsCOMPtr<nsINode> curParentParent = curParent->GetParentNode();
|
||||
int32_t parentOffset = curParentParent
|
||||
? curParentParent->IndexOf(curParent) : -1;
|
||||
curPositionedDiv = htmlEditor->CreateNode(nsGkAtoms::div, curParentParent,
|
||||
parentOffset);
|
||||
EditorRawDOMPoint atCurParent(curParent);
|
||||
curPositionedDiv =
|
||||
htmlEditor->CreateNode(nsGkAtoms::div, atCurParent);
|
||||
mNewBlock = curPositionedDiv;
|
||||
}
|
||||
EditorRawDOMPoint atEndOfCurPositionedDiv(curPositionedDiv,
|
||||
curPositionedDiv->Length());
|
||||
curList = htmlEditor->CreateNode(curParent->NodeInfo()->NameAtom(),
|
||||
curPositionedDiv, -1,
|
||||
curPositionedDiv->GetLastChild());
|
||||
atEndOfCurPositionedDiv);
|
||||
NS_ENSURE_STATE(curList);
|
||||
// curList is now the correct thing to put curNode in. Remember our
|
||||
// new block for postprocessing.
|
||||
|
@ -9128,18 +9151,15 @@ HTMLEditRules::WillAbsolutePosition(Selection& aSelection,
|
|||
offset);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!curPositionedDiv) {
|
||||
nsCOMPtr<nsINode> curParentParent = curParent->GetParentNode();
|
||||
int32_t parentOffset = curParentParent ?
|
||||
curParentParent->IndexOf(curParent) : -1;
|
||||
curPositionedDiv = htmlEditor->CreateNode(nsGkAtoms::div,
|
||||
curParentParent,
|
||||
parentOffset,
|
||||
curParent->AsContent());
|
||||
EditorRawDOMPoint atCurParent(curParent);
|
||||
curPositionedDiv =
|
||||
htmlEditor->CreateNode(nsGkAtoms::div, atCurParent);
|
||||
mNewBlock = curPositionedDiv;
|
||||
}
|
||||
EditorRawDOMPoint atEndOfCurPositionedDiv(curPositionedDiv,
|
||||
curPositionedDiv->Length());
|
||||
curList = htmlEditor->CreateNode(curParent->NodeInfo()->NameAtom(),
|
||||
curPositionedDiv, -1,
|
||||
curPositionedDiv->GetLastChild());
|
||||
atEndOfCurPositionedDiv);
|
||||
NS_ENSURE_STATE(curList);
|
||||
}
|
||||
rv = htmlEditor->MoveNode(listItem, curList, -1);
|
||||
|
@ -9160,8 +9180,8 @@ HTMLEditRules::WillAbsolutePosition(Selection& aSelection,
|
|||
rv = SplitAsNeeded(*nsGkAtoms::div, curParent, offset,
|
||||
address_of(curChild));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
curPositionedDiv = htmlEditor->CreateNode(nsGkAtoms::div, curParent,
|
||||
offset, curChild);
|
||||
EditorRawDOMPoint atCurChild(curParent, curChild, offset);
|
||||
curPositionedDiv = htmlEditor->CreateNode(nsGkAtoms::div, atCurChild);
|
||||
NS_ENSURE_STATE(curPositionedDiv);
|
||||
// Remember our new block for postprocessing
|
||||
mNewBlock = curPositionedDiv;
|
||||
|
|
|
@ -106,10 +106,9 @@ public:
|
|||
|
||||
// nsIEditActionListener methods
|
||||
|
||||
NS_IMETHOD WillCreateNode(const nsAString& aTag, nsIDOMNode* aParent,
|
||||
int32_t aPosition) override;
|
||||
NS_IMETHOD DidCreateNode(const nsAString& aTag, nsIDOMNode* aNode,
|
||||
nsIDOMNode* aParent, int32_t aPosition,
|
||||
NS_IMETHOD WillCreateNode(const nsAString& aTag,
|
||||
nsIDOMNode* aNextSiblingOfNewNode) override;
|
||||
NS_IMETHOD DidCreateNode(const nsAString& aTag, nsIDOMNode* aNewNode,
|
||||
nsresult aResult) override;
|
||||
NS_IMETHOD WillInsertNode(nsIDOMNode* aNode, nsIDOMNode* aParent,
|
||||
int32_t aPosition) override;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "mozilla/HTMLEditor.h"
|
||||
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/EditorDOMPoint.h"
|
||||
#include "mozilla/EventStates.h"
|
||||
#include "mozilla/TextEvents.h"
|
||||
|
||||
|
@ -2021,11 +2022,13 @@ HTMLEditor::MakeOrChangeList(const nsAString& aListType,
|
|||
}
|
||||
|
||||
// make a list
|
||||
nsCOMPtr<Element> newList = CreateNode(listAtom, parent, offset, child);
|
||||
MOZ_DIAGNOSTIC_ASSERT(child);
|
||||
EditorRawDOMPoint atChild(parent, child, offset);
|
||||
RefPtr<Element> newList = CreateNode(listAtom, atChild);
|
||||
NS_ENSURE_STATE(newList);
|
||||
// make a list item
|
||||
nsCOMPtr<Element> newItem = CreateNode(nsGkAtoms::li, newList, 0,
|
||||
newList->GetFirstChild());
|
||||
EditorRawDOMPoint atStartOfNewList(newList, 0);
|
||||
RefPtr<Element> newItem = CreateNode(nsGkAtoms::li, atStartOfNewList);
|
||||
NS_ENSURE_STATE(newItem);
|
||||
rv = selection->Collapse(newItem, 0);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
@ -2164,7 +2167,9 @@ HTMLEditor::InsertBasicBlock(const nsAString& aBlockType)
|
|||
}
|
||||
|
||||
// make a block
|
||||
nsCOMPtr<Element> newBlock = CreateNode(blockAtom, parent, offset, child);
|
||||
MOZ_DIAGNOSTIC_ASSERT(child);
|
||||
EditorRawDOMPoint atChild(parent, child, offset);
|
||||
RefPtr<Element> newBlock = CreateNode(blockAtom, atChild);
|
||||
NS_ENSURE_STATE(newBlock);
|
||||
|
||||
// reposition selection to inside the block
|
||||
|
@ -2238,8 +2243,9 @@ HTMLEditor::Indent(const nsAString& aIndent)
|
|||
}
|
||||
|
||||
// make a blockquote
|
||||
nsCOMPtr<Element> newBQ =
|
||||
CreateNode(nsGkAtoms::blockquote, parent, offset, child);
|
||||
MOZ_DIAGNOSTIC_ASSERT(child);
|
||||
EditorRawDOMPoint atChild(parent, child, offset);
|
||||
RefPtr<Element> newBQ = CreateNode(nsGkAtoms::blockquote, atChild);
|
||||
NS_ENSURE_STATE(newBQ);
|
||||
// put a space in it so layout will draw the list item
|
||||
rv = selection->Collapse(newBQ, 0);
|
||||
|
@ -4532,9 +4538,9 @@ HTMLEditor::CopyLastEditableChildStyles(nsINode* aPreviousBlock,
|
|||
childElement->NodeInfo()->NameAtom());
|
||||
NS_ENSURE_STATE(newStyles);
|
||||
} else {
|
||||
EditorRawDOMPoint atStartOfNewBlock(newBlock, 0);
|
||||
deepestStyle = newStyles =
|
||||
CreateNode(childElement->NodeInfo()->NameAtom(), newBlock, 0,
|
||||
newBlock->GetFirstChild());
|
||||
CreateNode(childElement->NodeInfo()->NameAtom(), atStartOfNewBlock);
|
||||
NS_ENSURE_STATE(newStyles);
|
||||
}
|
||||
CloneAttributes(newStyles, childElement);
|
||||
|
|
|
@ -242,7 +242,8 @@ TextEditor::SetDocumentCharacterSet(const nsACString& characterSet)
|
|||
}
|
||||
|
||||
// Create a new meta charset tag
|
||||
RefPtr<Element> metaElement = CreateNode(nsGkAtoms::meta, headNode, 0);
|
||||
EditorRawDOMPoint atStartOfHeadNode(headNode, 0);
|
||||
RefPtr<Element> metaElement = CreateNode(nsGkAtoms::meta, atStartOfHeadNode);
|
||||
if (NS_WARN_IF(!metaElement)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -443,14 +444,15 @@ TextEditor::CreateBRImpl(nsCOMPtr<nsIDOMNode>* aInOutParent,
|
|||
int32_t theOffset = *aInOutOffset;
|
||||
RefPtr<Element> brNode;
|
||||
if (IsTextNode(node)) {
|
||||
int32_t offset;
|
||||
nsCOMPtr<nsINode> tmp = GetNodeLocation(node, &offset);
|
||||
NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
|
||||
EditorRawDOMPoint atNode(node);
|
||||
if (NS_WARN_IF(!atNode.IsSetAndValid())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
if (!theOffset) {
|
||||
// we are already set to go
|
||||
} else if (theOffset == static_cast<int32_t>(node->Length())) {
|
||||
// update offset to point AFTER the text node
|
||||
offset++;
|
||||
atNode.AdvanceOffset();
|
||||
} else {
|
||||
// split the text node
|
||||
ErrorResult rv;
|
||||
|
@ -458,17 +460,19 @@ TextEditor::CreateBRImpl(nsCOMPtr<nsIDOMNode>* aInOutParent,
|
|||
if (NS_WARN_IF(rv.Failed())) {
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
tmp = GetNodeLocation(node, &offset);
|
||||
atNode.Clear();
|
||||
atNode.Set(node);
|
||||
}
|
||||
// create br
|
||||
brNode = CreateNode(nsGkAtoms::br, tmp, offset);
|
||||
brNode = CreateNode(nsGkAtoms::br, atNode);
|
||||
if (NS_WARN_IF(!brNode)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
*aInOutParent = GetAsDOMNode(tmp);
|
||||
*aInOutOffset = offset+1;
|
||||
*aInOutParent = GetAsDOMNode(atNode.Container());
|
||||
*aInOutOffset = atNode.Offset() + 1;
|
||||
} else {
|
||||
brNode = CreateNode(nsGkAtoms::br, node, theOffset);
|
||||
EditorRawDOMPoint atTheOffset(node, theOffset);
|
||||
brNode = CreateNode(nsGkAtoms::br, atTheOffset);
|
||||
if (NS_WARN_IF(!brNode)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
|
|
@ -30,31 +30,23 @@ interface nsIEditActionListener : nsISupports{
|
|||
|
||||
/**
|
||||
* Called before the editor creates a node.
|
||||
* @param aTag The tag name of the DOM Node to create.
|
||||
* @param aParent The node to insert the new object into
|
||||
* @param aPosition The place in aParent to insert the new node
|
||||
* 0=first child, 1=second child, etc.
|
||||
* any number > number of current children = last child
|
||||
* @param aTag The tag name of the DOM Node to create.
|
||||
* @param aNextSiblingOfNewNode The node which will be next sibling of
|
||||
* new node. If the new node will be appended,
|
||||
* this is null.
|
||||
*/
|
||||
void WillCreateNode(in DOMString aTag,
|
||||
in nsIDOMNode aParent,
|
||||
in long aPosition);
|
||||
in nsIDOMNode aNextSiblingOfNewNode);
|
||||
|
||||
/**
|
||||
* Called after the editor creates a node.
|
||||
* @param aTag The tag name of the DOM Node to create.
|
||||
* @param aNode The DOM Node that was created.
|
||||
* @param aParent The node to insert the new object into
|
||||
* @param aPosition The place in aParent to insert the new node
|
||||
* 0=first child, 1=second child, etc.
|
||||
* any number > number of current children = last child
|
||||
* @param aNewNode The DOM Node that was created.
|
||||
* @param aResult The result of the create node operation.
|
||||
*/
|
||||
void DidCreateNode(in DOMString aTag,
|
||||
in nsIDOMNode aNode,
|
||||
in nsIDOMNode aParent,
|
||||
in long aPosition,
|
||||
in nsresult aResult);
|
||||
in nsIDOMNode aNewNode,
|
||||
in nsresult aResult);
|
||||
|
||||
/**
|
||||
* Called before the editor inserts a node.
|
||||
|
|
|
@ -3373,13 +3373,16 @@ nsTextServicesDocument::WillJoinNodes(nsIDOMNode *aLeftNode,
|
|||
// -------------------------------
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTextServicesDocument::WillCreateNode(const nsAString& aTag, nsIDOMNode *aParent, int32_t aPosition)
|
||||
nsTextServicesDocument::WillCreateNode(const nsAString& aTag,
|
||||
nsIDOMNode* aNextSiblingOfNewNode)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTextServicesDocument::DidCreateNode(const nsAString& aTag, nsIDOMNode *aNode, nsIDOMNode *aParent, int32_t aPosition, nsresult aResult)
|
||||
nsTextServicesDocument::DidCreateNode(const nsAString& aTag,
|
||||
nsIDOMNode* aNewNode,
|
||||
nsresult aResult)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -118,8 +118,11 @@ public:
|
|||
nsIDOMNode *aParent,
|
||||
nsresult aResult) override;
|
||||
// these listen methods are unused:
|
||||
NS_IMETHOD WillCreateNode(const nsAString& aTag, nsIDOMNode *aParent, int32_t aPosition) override;
|
||||
NS_IMETHOD DidCreateNode(const nsAString& aTag, nsIDOMNode *aNode, nsIDOMNode *aParent, int32_t aPosition, nsresult aResult) override;
|
||||
NS_IMETHOD WillCreateNode(const nsAString& aTag,
|
||||
nsIDOMNode* aNextSiblingOfNewNode) override;
|
||||
NS_IMETHOD DidCreateNode(const nsAString& aTag,
|
||||
nsIDOMNode* aNewNode,
|
||||
nsresult aResult) override;
|
||||
NS_IMETHOD WillInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString &aString) override;
|
||||
NS_IMETHOD DidInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString &aString, nsresult aResult) override;
|
||||
NS_IMETHOD WillDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength) override;
|
||||
|
|
|
@ -1054,13 +1054,17 @@ mozInlineSpellChecker::IgnoreWords(const char16_t **aWordsToIgnore,
|
|||
return ScheduleSpellCheck(Move(status));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP mozInlineSpellChecker::WillCreateNode(const nsAString & aTag, nsIDOMNode *aParent, int32_t aPosition)
|
||||
NS_IMETHODIMP
|
||||
mozInlineSpellChecker::WillCreateNode(const nsAString& aTag,
|
||||
nsIDOMNode* aNextSiblingOfNewNode)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP mozInlineSpellChecker::DidCreateNode(const nsAString & aTag, nsIDOMNode *aNode, nsIDOMNode *aParent,
|
||||
int32_t aPosition, nsresult aResult)
|
||||
NS_IMETHODIMP
|
||||
mozInlineSpellChecker::DidCreateNode(const nsAString& aTag,
|
||||
nsIDOMNode* aNewNode,
|
||||
nsresult aResult)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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 jsfixedsizehash_h_
|
||||
#define jsfixedsizehash_h_
|
||||
|
||||
#include "ds/LifoAlloc.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
/*
|
||||
* Class representing a hash set with fixed capacity, with newer entries
|
||||
* evicting older entries. Each entry has several hashes and can be stored in
|
||||
* different buckets, with the choice of which to evict on insertion being
|
||||
* managed via LRU. For tables with a relatively small size, using different
|
||||
* hashes increases utilization and makes it less likely that entries will keep
|
||||
* evicting each other due to wanting to use the same bucket.
|
||||
*
|
||||
* T indicates the type of hash elements, HashPolicy must have the following
|
||||
* contents:
|
||||
*
|
||||
* Lookup - As for HashMap / HashSet.
|
||||
*
|
||||
* bool match(T, Lookup) - As for HashMap / HashSet.
|
||||
*
|
||||
* NumHashes - Number of different hashes generated for each entry.
|
||||
*
|
||||
* void hash(Lookup, HashNumber[NumHashes]) - Compute all hashes for an entry.
|
||||
*
|
||||
* void clear(T*) - Clear an entry, such that isCleared() holds afterwards.
|
||||
*
|
||||
* bool isCleared(T) - Test whether an entry has been cleared.
|
||||
*/
|
||||
template <class T, class HashPolicy, size_t Capacity>
|
||||
class FixedSizeHashSet
|
||||
{
|
||||
T entries[Capacity];
|
||||
uint32_t lastOperations[Capacity];
|
||||
uint32_t numOperations;
|
||||
|
||||
static const size_t NumHashes = HashPolicy::NumHashes;
|
||||
|
||||
static_assert(Capacity > 0, "an empty fixed-size hash set is meaningless");
|
||||
|
||||
public:
|
||||
typedef typename HashPolicy::Lookup Lookup;
|
||||
|
||||
FixedSizeHashSet()
|
||||
: entries(), lastOperations(), numOperations(0)
|
||||
{
|
||||
MOZ_ASSERT(HashPolicy::isCleared(entries[0]));
|
||||
}
|
||||
|
||||
bool lookup(const Lookup& lookup, T* pentry)
|
||||
{
|
||||
size_t bucket;
|
||||
if (lookupReference(lookup, &bucket)) {
|
||||
*pentry = entries[bucket];
|
||||
lastOperations[bucket] = numOperations++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void insert(const Lookup& lookup, const T& entry)
|
||||
{
|
||||
size_t buckets[NumHashes];
|
||||
getBuckets(lookup, buckets);
|
||||
|
||||
size_t min = buckets[0];
|
||||
for (size_t i = 0; i < NumHashes; i++) {
|
||||
const T& entry = entries[buckets[i]];
|
||||
if (HashPolicy::isCleared(entry)) {
|
||||
entries[buckets[i]] = entry;
|
||||
lastOperations[buckets[i]] = numOperations++;
|
||||
return;
|
||||
}
|
||||
if (i && lastOperations[min] > lastOperations[buckets[i]])
|
||||
min = buckets[i];
|
||||
}
|
||||
|
||||
entries[min] = entry;
|
||||
lastOperations[min] = numOperations++;
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
void remove(const S& s)
|
||||
{
|
||||
size_t bucket;
|
||||
if (lookupReference(s, &bucket))
|
||||
HashPolicy::clear(&entries[bucket]);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename S>
|
||||
bool lookupReference(const S& s, size_t* pbucket)
|
||||
{
|
||||
size_t buckets[NumHashes];
|
||||
getBuckets(s, buckets);
|
||||
|
||||
for (size_t i = 0; i < NumHashes; i++) {
|
||||
const T& entry = entries[buckets[i]];
|
||||
if (!HashPolicy::isCleared(entry) && HashPolicy::match(entry, s)) {
|
||||
*pbucket = buckets[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
void getBuckets(const S& s, size_t buckets[NumHashes])
|
||||
{
|
||||
HashNumber hashes[NumHashes];
|
||||
HashPolicy::hash(s, hashes);
|
||||
|
||||
for (size_t i = 0; i < NumHashes; i++)
|
||||
buckets[i] = hashes[i] % Capacity;
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* jsfixedsizehash_h_ */
|
|
@ -0,0 +1,28 @@
|
|||
// |jit-test| --arm-asm-nop-fill=1
|
||||
//
|
||||
try {
|
||||
enableSingleStepProfiling();
|
||||
disableSingleStepProfiling();
|
||||
} catch (e) {
|
||||
// Early quit on plateforms not supporting single step profiling.
|
||||
quit();
|
||||
}
|
||||
|
||||
load(libdir + "asm.js");
|
||||
|
||||
var ffi = function(enable) {
|
||||
enableGeckoProfiling();
|
||||
enableSingleStepProfiling();
|
||||
}
|
||||
var f = asmLink(asmCompile('global', 'ffis',
|
||||
USE_ASM + `
|
||||
var ffi=ffis.ffi;
|
||||
function f(i) {
|
||||
i=i|0;
|
||||
ffi(i|0);
|
||||
} return f
|
||||
`), null, {
|
||||
ffi
|
||||
});
|
||||
f(0);
|
||||
f(+1);
|
|
@ -1592,34 +1592,6 @@ JSFunction::createScriptForLazilyInterpretedFunction(JSContext* cx, HandleFuncti
|
|||
return true;
|
||||
}
|
||||
|
||||
// Lazy script caching is only supported for leaf functions. If a
|
||||
// script with inner functions was returned by the cache, those inner
|
||||
// functions would be delazified when deep cloning the script, even if
|
||||
// they have never executed.
|
||||
//
|
||||
// Additionally, the lazy script cache is not used during incremental
|
||||
// GCs, to avoid resurrecting dead scripts after incremental sweeping
|
||||
// has started.
|
||||
if (canRelazify && !JS::IsIncrementalGCInProgress(cx)) {
|
||||
LazyScriptCache::Lookup lookup(cx, lazy);
|
||||
cx->caches().lazyScriptCache.lookup(lookup, script.address());
|
||||
}
|
||||
|
||||
if (script) {
|
||||
RootedScope enclosingScope(cx, lazy->enclosingScope());
|
||||
RootedScript clonedScript(cx, CloneScriptIntoFunction(cx, enclosingScope, fun, script));
|
||||
if (!clonedScript)
|
||||
return false;
|
||||
|
||||
clonedScript->setSourceObject(lazy->sourceObject());
|
||||
|
||||
fun->initAtom(script->functionNonDelazifying()->displayAtom());
|
||||
|
||||
if (!lazy->maybeScript())
|
||||
lazy->initScript(clonedScript);
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(lazy->scriptSource()->hasSourceData());
|
||||
|
||||
// Parse and compile the script from source.
|
||||
|
@ -1654,9 +1626,6 @@ JSFunction::createScriptForLazilyInterpretedFunction(JSContext* cx, HandleFuncti
|
|||
// script is encountered later a match can be determined.
|
||||
script->setColumn(lazy->column());
|
||||
|
||||
LazyScriptCache::Lookup lookup(cx, lazy);
|
||||
cx->caches().lazyScriptCache.insert(lookup, script);
|
||||
|
||||
// Remember the lazy script on the compiled script, so it can be
|
||||
// stored on the function again in case of re-lazification.
|
||||
// Only functions without inner functions are re-lazified.
|
||||
|
|
|
@ -3205,8 +3205,6 @@ JSScript::finalize(FreeOp* fop)
|
|||
if (scriptData_)
|
||||
scriptData_->decRefCount();
|
||||
|
||||
fop->runtime()->caches().lazyScriptCache.remove(this);
|
||||
|
||||
// In most cases, our LazyScript's script pointer will reference this
|
||||
// script, and thus be nulled out by normal weakref processing. However, if
|
||||
// we unlazified the LazyScript during incremental sweeping, it will have a
|
||||
|
@ -4529,76 +4527,6 @@ JSScript::mayReadFrameArgsDirectly()
|
|||
return argumentsHasVarBinding() || hasRest();
|
||||
}
|
||||
|
||||
static inline void
|
||||
LazyScriptHash(uint32_t lineno, uint32_t column, uint32_t begin, uint32_t end,
|
||||
HashNumber hashes[3])
|
||||
{
|
||||
HashNumber hash = lineno;
|
||||
hash = RotateLeft(hash, 4) ^ column;
|
||||
hash = RotateLeft(hash, 4) ^ begin;
|
||||
hash = RotateLeft(hash, 4) ^ end;
|
||||
|
||||
hashes[0] = hash;
|
||||
hashes[1] = RotateLeft(hashes[0], 4) ^ begin;
|
||||
hashes[2] = RotateLeft(hashes[1], 4) ^ end;
|
||||
}
|
||||
|
||||
void
|
||||
LazyScriptHashPolicy::hash(const Lookup& lookup, HashNumber hashes[3])
|
||||
{
|
||||
LazyScript* lazy = lookup.lazy;
|
||||
LazyScriptHash(lazy->lineno(), lazy->column(), lazy->begin(), lazy->end(), hashes);
|
||||
}
|
||||
|
||||
void
|
||||
LazyScriptHashPolicy::hash(JSScript* script, HashNumber hashes[3])
|
||||
{
|
||||
LazyScriptHash(script->lineno(), script->column(), script->sourceStart(), script->sourceEnd(), hashes);
|
||||
}
|
||||
|
||||
bool
|
||||
LazyScriptHashPolicy::match(JSScript* script, const Lookup& lookup)
|
||||
{
|
||||
JSContext* cx = lookup.cx;
|
||||
LazyScript* lazy = lookup.lazy;
|
||||
|
||||
// To be a match, the script and lazy script need to have the same line
|
||||
// and column and to be at the same position within their respective
|
||||
// source blobs, and to have the same source contents and version.
|
||||
//
|
||||
// While the surrounding code in the source may differ, this is
|
||||
// sufficient to ensure that compiling the lazy script will yield an
|
||||
// identical result to compiling the original script.
|
||||
//
|
||||
// Note that the filenames and origin principals of the lazy script and
|
||||
// original script can differ. If there is a match, these will be fixed
|
||||
// up in the resulting clone by the caller.
|
||||
|
||||
if (script->lineno() != lazy->lineno() ||
|
||||
script->column() != lazy->column() ||
|
||||
script->getVersion() != lazy->version() ||
|
||||
script->sourceStart() != lazy->begin() ||
|
||||
script->sourceEnd() != lazy->end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UncompressedSourceCache::AutoHoldEntry holder;
|
||||
|
||||
size_t scriptBegin = script->sourceStart();
|
||||
size_t length = script->sourceEnd() - scriptBegin;
|
||||
ScriptSource::PinnedChars scriptChars(cx, script->scriptSource(), holder, scriptBegin, length);
|
||||
if (!scriptChars.get())
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(scriptBegin == lazy->begin());
|
||||
ScriptSource::PinnedChars lazyChars(cx, lazy->scriptSource(), holder, scriptBegin, length);
|
||||
if (!lazyChars.get())
|
||||
return false;
|
||||
|
||||
return !memcmp(scriptChars.get(), lazyChars.get(), length);
|
||||
}
|
||||
|
||||
void
|
||||
JSScript::AutoDelazify::holdScript(JS::HandleFunction fun)
|
||||
{
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "jsobj.h"
|
||||
#include "jsscript.h"
|
||||
|
||||
#include "ds/FixedSizeHash.h"
|
||||
#include "frontend/SourceNotes.h"
|
||||
#include "gc/Tracer.h"
|
||||
#include "js/RootingAPI.h"
|
||||
|
@ -86,33 +85,6 @@ struct EvalCacheHashPolicy
|
|||
|
||||
typedef HashSet<EvalCacheEntry, EvalCacheHashPolicy, SystemAllocPolicy> EvalCache;
|
||||
|
||||
struct LazyScriptHashPolicy
|
||||
{
|
||||
struct Lookup {
|
||||
JSContext* cx;
|
||||
LazyScript* lazy;
|
||||
|
||||
Lookup(JSContext* cx, LazyScript* lazy)
|
||||
: cx(cx), lazy(lazy)
|
||||
{}
|
||||
};
|
||||
|
||||
static const size_t NumHashes = 3;
|
||||
|
||||
static void hash(const Lookup& lookup, HashNumber hashes[NumHashes]);
|
||||
static bool match(JSScript* script, const Lookup& lookup);
|
||||
|
||||
// Alternate methods for use when removing scripts from the hash without an
|
||||
// explicit LazyScript lookup.
|
||||
static void hash(JSScript* script, HashNumber hashes[NumHashes]);
|
||||
static bool match(JSScript* script, JSScript* lookup) { return script == lookup; }
|
||||
|
||||
static void clear(JSScript** pscript) { *pscript = nullptr; }
|
||||
static bool isCleared(JSScript* script) { return !script; }
|
||||
};
|
||||
|
||||
typedef FixedSizeHashSet<JSScript*, LazyScriptHashPolicy, 769> LazyScriptCache;
|
||||
|
||||
/*
|
||||
* Cache for speeding up repetitive creation of objects in the VM.
|
||||
* When an object is created which matches the criteria in the 'key' section
|
||||
|
@ -256,7 +228,6 @@ class RuntimeCaches
|
|||
js::NewObjectCache newObjectCache;
|
||||
js::UncompressedSourceCache uncompressedSourceCache;
|
||||
js::EvalCache evalCache;
|
||||
LazyScriptCache lazyScriptCache;
|
||||
|
||||
bool init();
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
#include "builtin/AtomicsObject.h"
|
||||
#include "builtin/Intl.h"
|
||||
#include "builtin/Promise.h"
|
||||
#include "ds/FixedSizeHash.h"
|
||||
#include "frontend/NameCollections.h"
|
||||
#include "gc/GCRuntime.h"
|
||||
#include "gc/Tracer.h"
|
||||
|
|
|
@ -759,6 +759,8 @@ CodeRange::CodeRange(uint32_t funcIndex, JitExitOffsets offsets)
|
|||
u.funcIndex_ = funcIndex;
|
||||
u.jitExit.beginToUntrustedFPStart_ = offsets.untrustedFPStart - begin_;
|
||||
u.jitExit.beginToUntrustedFPEnd_ = offsets.untrustedFPEnd - begin_;
|
||||
MOZ_ASSERT(jitExitUntrustedFPStart() == offsets.untrustedFPStart);
|
||||
MOZ_ASSERT(jitExitUntrustedFPEnd() == offsets.untrustedFPEnd);
|
||||
}
|
||||
|
||||
CodeRange::CodeRange(Trap trap, CallableOffsets offsets)
|
||||
|
|
|
@ -1021,8 +1021,8 @@ class CodeRange
|
|||
uint8_t beginToTierEntry_;
|
||||
} func;
|
||||
struct {
|
||||
uint8_t beginToUntrustedFPStart_;
|
||||
uint8_t beginToUntrustedFPEnd_;
|
||||
uint16_t beginToUntrustedFPStart_;
|
||||
uint16_t beginToUntrustedFPEnd_;
|
||||
} jitExit;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include "nsListControlFrame.h"
|
||||
#include "nsCheckboxRadioFrame.h" // for COMPARE macro
|
||||
#include "nsGkAtoms.h"
|
||||
#include "nsIDOMHTMLOptionElement.h"
|
||||
#include "nsComboboxControlFrame.h"
|
||||
#include "nsIPresShell.h"
|
||||
#include "nsIDOMMouseEvent.h"
|
||||
|
@ -231,12 +230,9 @@ void nsListControlFrame::PaintFocus(DrawTarget* aDrawTarget, nsPoint aPt)
|
|||
fRect += aPt;
|
||||
|
||||
bool lastItemIsSelected = false;
|
||||
if (focusedContent) {
|
||||
nsCOMPtr<nsIDOMHTMLOptionElement> domOpt =
|
||||
do_QueryInterface(focusedContent);
|
||||
if (domOpt) {
|
||||
domOpt->GetSelected(&lastItemIsSelected);
|
||||
}
|
||||
HTMLOptionElement* domOpt = HTMLOptionElement::FromContentOrNull(focusedContent);
|
||||
if (domOpt) {
|
||||
lastItemIsSelected = domOpt->Selected();
|
||||
}
|
||||
|
||||
// set up back stop colors and then ask L&F service for the real colors
|
||||
|
@ -1857,7 +1853,8 @@ nsListControlFrame::MouseDown(nsIDOMEvent* aMouseEvent)
|
|||
if (mComboboxFrame->IsOpenInParentProcess()) {
|
||||
nsCOMPtr<nsIDOMEventTarget> etarget;
|
||||
aMouseEvent->GetTarget(getter_AddRefs(etarget));
|
||||
nsCOMPtr<nsIDOMHTMLOptionElement> option = do_QueryInterface(etarget);
|
||||
nsCOMPtr<nsIContent> econtent = do_QueryInterface(etarget);
|
||||
HTMLOptionElement* option = HTMLOptionElement::FromContentOrNull(econtent);
|
||||
if (option) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -2453,11 +2450,11 @@ nsListControlFrame::KeyPress(nsIDOMEvent* aKeyEvent)
|
|||
}
|
||||
|
||||
nsAutoString text;
|
||||
if (NS_FAILED(optionElement->GetText(text)) ||
|
||||
!StringBeginsWith(
|
||||
nsContentUtils::TrimWhitespace<
|
||||
nsContentUtils::IsHTMLWhitespaceOrNBSP>(text, false),
|
||||
incrementalString, nsCaseInsensitiveStringComparator())) {
|
||||
optionElement->GetText(text);
|
||||
if (!StringBeginsWith(
|
||||
nsContentUtils::TrimWhitespace<
|
||||
nsContentUtils::IsHTMLWhitespaceOrNBSP>(text, false),
|
||||
incrementalString, nsCaseInsensitiveStringComparator())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ class PublicSuffixPatterns {
|
|||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e("Patterns", "IOException during loading public suffix list");
|
||||
throw new IllegalStateException("resource publicsuffixlist could not be opened but is bundled with app", e);
|
||||
} finally {
|
||||
IOUtils.safeStreamClose(reader);
|
||||
}
|
||||
|
|
|
@ -161,12 +161,12 @@ class LoggingManager(object):
|
|||
# complaining about "no handlers could be found for logger XXX."
|
||||
self.root_logger.addHandler(logging.NullHandler())
|
||||
|
||||
self.mach_logger = logging.getLogger('mach')
|
||||
self.mach_logger.setLevel(logging.DEBUG)
|
||||
mach_logger = logging.getLogger('mach')
|
||||
mach_logger.setLevel(logging.DEBUG)
|
||||
|
||||
self.structured_filter = ConvertToStructuredFilter()
|
||||
|
||||
self.structured_loggers = [self.mach_logger]
|
||||
self.structured_loggers = [mach_logger]
|
||||
|
||||
self._terminal = None
|
||||
|
||||
|
@ -256,10 +256,43 @@ class LoggingManager(object):
|
|||
self.terminal_handler.removeFilter(self.structured_filter)
|
||||
self.root_logger.removeHandler(self.terminal_handler)
|
||||
|
||||
def register_structured_logger(self, logger):
|
||||
def register_structured_logger(self, logger, terminal=True, json=True):
|
||||
"""Register a structured logger.
|
||||
|
||||
This needs to be called for all structured loggers that don't chain up
|
||||
to the mach logger in order for their output to be captured.
|
||||
"""
|
||||
self.structured_loggers.append(logger)
|
||||
|
||||
if terminal and self.terminal_handler:
|
||||
logger.addHandler(self.terminal_handler)
|
||||
|
||||
if json:
|
||||
for handler in self.json_handlers:
|
||||
logger.addHandler(handler)
|
||||
|
||||
def enable_all_structured_loggers(self, terminal=True, json=True):
|
||||
"""Enable logging of all structured messages from all loggers.
|
||||
|
||||
``terminal`` and ``json`` determine which log handlers to operate
|
||||
on. By default, all known handlers are operated on.
|
||||
"""
|
||||
# Remove current handlers from all loggers so we don't double
|
||||
# register handlers.
|
||||
for logger in self.root_logger.manager.loggerDict.values():
|
||||
# Some entries might be logging.PlaceHolder.
|
||||
if not isinstance(logger, logging.Logger):
|
||||
continue
|
||||
|
||||
if terminal:
|
||||
logger.removeHandler(self.terminal_handler)
|
||||
|
||||
if json:
|
||||
for handler in self.json_handlers:
|
||||
logger.removeHandler(handler)
|
||||
|
||||
# Wipe out existing registered structured loggers since they
|
||||
# all propagate to root logger.
|
||||
self.structured_loggers = []
|
||||
self.register_structured_logger(self.root_logger, terminal=terminal,
|
||||
json=json)
|
||||
|
|
|
@ -8,36 +8,67 @@ import getpass
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import which
|
||||
|
||||
from collections import (
|
||||
Counter,
|
||||
namedtuple,
|
||||
OrderedDict,
|
||||
)
|
||||
from textwrap import (
|
||||
TextWrapper,
|
||||
)
|
||||
|
||||
try:
|
||||
import psutil
|
||||
except Exception:
|
||||
psutil = None
|
||||
|
||||
from mach.mixin.logging import LoggingMixin
|
||||
from mozsystemmonitor.resourcemonitor import SystemResourceMonitor
|
||||
|
||||
import mozpack.path as mozpath
|
||||
|
||||
from ..base import MozbuildObject
|
||||
|
||||
from ..testing import install_test_files
|
||||
|
||||
from ..base import (
|
||||
BuildEnvironmentNotFoundException,
|
||||
MozbuildObject,
|
||||
)
|
||||
from ..backend import (
|
||||
get_backend_class,
|
||||
)
|
||||
from ..testing import (
|
||||
install_test_files,
|
||||
)
|
||||
from ..compilation.warnings import (
|
||||
WarningsCollector,
|
||||
WarningsDatabase,
|
||||
)
|
||||
from ..shellutil import (
|
||||
quote as shell_quote,
|
||||
)
|
||||
from ..util import (
|
||||
mkdir,
|
||||
resolve_target_to_make,
|
||||
)
|
||||
|
||||
|
||||
FINDER_SLOW_MESSAGE = '''
|
||||
===================
|
||||
PERFORMANCE WARNING
|
||||
|
||||
The OS X Finder application (file indexing used by Spotlight) used a lot of CPU
|
||||
during the build - an average of %f%% (100%% is 1 core). This made your build
|
||||
slower.
|
||||
|
||||
Consider adding ".noindex" to the end of your object directory name to have
|
||||
Finder ignore it. Or, add an indexing exclusion through the Spotlight System
|
||||
Preferences.
|
||||
===================
|
||||
'''.strip()
|
||||
|
||||
from textwrap import TextWrapper
|
||||
|
||||
INSTALL_TESTS_CLOBBER = ''.join([TextWrapper().fill(line) + '\n' for line in
|
||||
'''
|
||||
|
@ -485,6 +516,275 @@ class BuildMonitor(MozbuildObject):
|
|||
return ccache_stats
|
||||
|
||||
|
||||
class TerminalLoggingHandler(logging.Handler):
|
||||
"""Custom logging handler that works with terminal window dressing.
|
||||
|
||||
This class should probably live elsewhere, like the mach core. Consider
|
||||
this a proving ground for its usefulness.
|
||||
"""
|
||||
def __init__(self):
|
||||
logging.Handler.__init__(self)
|
||||
|
||||
self.fh = sys.stdout
|
||||
self.footer = None
|
||||
|
||||
def flush(self):
|
||||
self.acquire()
|
||||
|
||||
try:
|
||||
self.fh.flush()
|
||||
finally:
|
||||
self.release()
|
||||
|
||||
def emit(self, record):
|
||||
msg = self.format(record)
|
||||
|
||||
self.acquire()
|
||||
|
||||
try:
|
||||
if self.footer:
|
||||
self.footer.clear()
|
||||
|
||||
self.fh.write(msg)
|
||||
self.fh.write('\n')
|
||||
|
||||
if self.footer:
|
||||
self.footer.draw()
|
||||
|
||||
# If we don't flush, the footer may not get drawn.
|
||||
self.fh.flush()
|
||||
finally:
|
||||
self.release()
|
||||
|
||||
|
||||
class Footer(object):
|
||||
"""Handles display of a footer in a terminal.
|
||||
|
||||
This class implements the functionality common to all mach commands
|
||||
that render a footer.
|
||||
"""
|
||||
|
||||
def __init__(self, terminal):
|
||||
# terminal is a blessings.Terminal.
|
||||
self._t = terminal
|
||||
self._fh = sys.stdout
|
||||
|
||||
def clear(self):
|
||||
"""Removes the footer from the current terminal."""
|
||||
self._fh.write(self._t.move_x(0))
|
||||
self._fh.write(self._t.clear_eol())
|
||||
|
||||
def write(self, parts):
|
||||
"""Write some output in the footer, accounting for terminal width.
|
||||
|
||||
parts is a list of 2-tuples of (encoding_function, input).
|
||||
None means no encoding."""
|
||||
|
||||
# We don't want to write more characters than the current width of the
|
||||
# terminal otherwise wrapping may result in weird behavior. We can't
|
||||
# simply truncate the line at terminal width characters because a)
|
||||
# non-viewable escape characters count towards the limit and b) we
|
||||
# don't want to truncate in the middle of an escape sequence because
|
||||
# subsequent output would inherit the escape sequence.
|
||||
max_width = self._t.width
|
||||
written = 0
|
||||
write_pieces = []
|
||||
for part in parts:
|
||||
try:
|
||||
func, part = part
|
||||
encoded = getattr(self._t, func)(part)
|
||||
except ValueError:
|
||||
encoded = part
|
||||
|
||||
len_part = len(part)
|
||||
len_spaces = len(write_pieces)
|
||||
if written + len_part + len_spaces > max_width:
|
||||
write_pieces.append(part[0:max_width - written - len_spaces])
|
||||
written += len_part
|
||||
break
|
||||
|
||||
write_pieces.append(encoded)
|
||||
written += len_part
|
||||
|
||||
with self._t.location():
|
||||
self._t.move(self._t.height-1,0)
|
||||
self._fh.write(' '.join(write_pieces))
|
||||
|
||||
|
||||
class BuildProgressFooter(Footer):
|
||||
"""Handles display of a build progress indicator in a terminal.
|
||||
|
||||
When mach builds inside a blessings-supported terminal, it will render
|
||||
progress information collected from a BuildMonitor. This class converts the
|
||||
state of BuildMonitor into terminal output.
|
||||
"""
|
||||
|
||||
def __init__(self, terminal, monitor):
|
||||
Footer.__init__(self, terminal)
|
||||
self.tiers = monitor.tiers.tier_status.viewitems()
|
||||
|
||||
def draw(self):
|
||||
"""Draws this footer in the terminal."""
|
||||
|
||||
if not self.tiers:
|
||||
return
|
||||
|
||||
# The drawn terminal looks something like:
|
||||
# TIER: static export libs tools
|
||||
|
||||
parts = [('bold', 'TIER:')]
|
||||
append = parts.append
|
||||
for tier, status in self.tiers:
|
||||
if status is None:
|
||||
append(tier)
|
||||
elif status == 'finished':
|
||||
append(('green', tier))
|
||||
else:
|
||||
append(('underline_yellow', tier))
|
||||
|
||||
self.write(parts)
|
||||
|
||||
|
||||
|
||||
class OutputManager(LoggingMixin):
|
||||
"""Handles writing job output to a terminal or log."""
|
||||
|
||||
def __init__(self, log_manager, footer):
|
||||
self.populate_logger()
|
||||
|
||||
self.footer = None
|
||||
terminal = log_manager.terminal
|
||||
|
||||
# TODO convert terminal footer to config file setting.
|
||||
if not terminal or os.environ.get('MACH_NO_TERMINAL_FOOTER', None):
|
||||
return
|
||||
if os.environ.get('INSIDE_EMACS', None):
|
||||
return
|
||||
|
||||
self.t = terminal
|
||||
self.footer = footer
|
||||
|
||||
self._handler = TerminalLoggingHandler()
|
||||
self._handler.setFormatter(log_manager.terminal_formatter)
|
||||
self._handler.footer = self.footer
|
||||
|
||||
old = log_manager.replace_terminal_handler(self._handler)
|
||||
self._handler.level = old.level
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if self.footer:
|
||||
self.footer.clear()
|
||||
# Prevents the footer from being redrawn if logging occurs.
|
||||
self._handler.footer = None
|
||||
|
||||
def write_line(self, line):
|
||||
if self.footer:
|
||||
self.footer.clear()
|
||||
|
||||
print(line)
|
||||
|
||||
if self.footer:
|
||||
self.footer.draw()
|
||||
|
||||
def refresh(self):
|
||||
if not self.footer:
|
||||
return
|
||||
|
||||
self.footer.clear()
|
||||
self.footer.draw()
|
||||
|
||||
|
||||
class BuildOutputManager(OutputManager):
|
||||
"""Handles writing build output to a terminal, to logs, etc."""
|
||||
|
||||
def __init__(self, log_manager, monitor, footer):
|
||||
self.monitor = monitor
|
||||
OutputManager.__init__(self, log_manager, footer)
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
OutputManager.__exit__(self, exc_type, exc_value, traceback)
|
||||
|
||||
# Ensure the resource monitor is stopped because leaving it running
|
||||
# could result in the process hanging on exit because the resource
|
||||
# collection child process hasn't been told to stop.
|
||||
self.monitor.stop_resource_recording()
|
||||
|
||||
|
||||
def on_line(self, line):
|
||||
warning, state_changed, relevant = self.monitor.on_line(line)
|
||||
|
||||
if relevant:
|
||||
self.log(logging.INFO, 'build_output', {'line': line}, '{line}')
|
||||
elif state_changed:
|
||||
have_handler = hasattr(self, 'handler')
|
||||
if have_handler:
|
||||
self.handler.acquire()
|
||||
try:
|
||||
self.refresh()
|
||||
finally:
|
||||
if have_handler:
|
||||
self.handler.release()
|
||||
|
||||
|
||||
class StaticAnalysisFooter(Footer):
|
||||
"""Handles display of a static analysis progress indicator in a terminal.
|
||||
"""
|
||||
|
||||
def __init__(self, terminal, monitor):
|
||||
Footer.__init__(self, terminal)
|
||||
self.monitor = monitor
|
||||
|
||||
def draw(self):
|
||||
"""Draws this footer in the terminal."""
|
||||
|
||||
monitor = self.monitor
|
||||
total = monitor.num_files
|
||||
processed = monitor.num_files_processed
|
||||
percent = '(%.2f%%)' % (processed * 100.0 / total)
|
||||
parts = [
|
||||
('dim', 'Processing'),
|
||||
('yellow', str(processed)),
|
||||
('dim', 'of'),
|
||||
('yellow', str(total)),
|
||||
('dim', 'files'),
|
||||
('green', percent)
|
||||
]
|
||||
if monitor.current_file:
|
||||
parts.append(('bold', monitor.current_file))
|
||||
|
||||
self.write(parts)
|
||||
|
||||
|
||||
class StaticAnalysisOutputManager(OutputManager):
|
||||
"""Handles writing static analysis output to a terminal."""
|
||||
|
||||
def __init__(self, log_manager, monitor, footer):
|
||||
self.monitor = monitor
|
||||
OutputManager.__init__(self, log_manager, footer)
|
||||
|
||||
def on_line(self, line):
|
||||
warning, relevant = self.monitor.on_line(line)
|
||||
|
||||
if warning:
|
||||
self.log(logging.INFO, 'compiler_warning', warning,
|
||||
'Warning: {flag} in {filename}: {message}')
|
||||
|
||||
if relevant:
|
||||
self.log(logging.INFO, 'build_output', {'line': line}, '{line}')
|
||||
else:
|
||||
have_handler = hasattr(self, 'handler')
|
||||
if have_handler:
|
||||
self.handler.acquire()
|
||||
try:
|
||||
self.refresh()
|
||||
finally:
|
||||
if have_handler:
|
||||
self.handler.release()
|
||||
|
||||
|
||||
class CCacheStats(object):
|
||||
"""Holds statistics from ccache.
|
||||
|
||||
|
@ -678,6 +978,328 @@ class CCacheStats(object):
|
|||
class BuildDriver(MozbuildObject):
|
||||
"""Provides a high-level API for build actions."""
|
||||
|
||||
def build(self, what=None, disable_extra_make_dependencies=None, jobs=0,
|
||||
directory=None, verbose=False, keep_going=False, mach_context=None):
|
||||
"""Invoke the build backend.
|
||||
|
||||
``what`` defines the thing to build. If not defined, the default
|
||||
target is used.
|
||||
"""
|
||||
warnings_path = self._get_state_filename('warnings.json')
|
||||
monitor = self._spawn(BuildMonitor)
|
||||
monitor.init(warnings_path)
|
||||
ccache_start = monitor.ccache_stats()
|
||||
footer = BuildProgressFooter(self.log_manager.terminal, monitor)
|
||||
|
||||
# Disable indexing in objdir because it is not necessary and can slow
|
||||
# down builds.
|
||||
mkdir(self.topobjdir, not_indexed=True)
|
||||
|
||||
with BuildOutputManager(self.log_manager, monitor, footer) as output:
|
||||
monitor.start()
|
||||
|
||||
if directory is not None and not what:
|
||||
print('Can only use -C/--directory with an explicit target '
|
||||
'name.')
|
||||
return 1
|
||||
|
||||
if directory is not None:
|
||||
disable_extra_make_dependencies=True
|
||||
directory = mozpath.normsep(directory)
|
||||
if directory.startswith('/'):
|
||||
directory = directory[1:]
|
||||
|
||||
status = None
|
||||
monitor.start_resource_recording()
|
||||
if what:
|
||||
top_make = os.path.join(self.topobjdir, 'Makefile')
|
||||
if not os.path.exists(top_make):
|
||||
print('Your tree has not been configured yet. Please run '
|
||||
'|mach build| with no arguments.')
|
||||
return 1
|
||||
|
||||
# Collect target pairs.
|
||||
target_pairs = []
|
||||
for target in what:
|
||||
path_arg = self._wrap_path_argument(target)
|
||||
|
||||
if directory is not None:
|
||||
make_dir = os.path.join(self.topobjdir, directory)
|
||||
make_target = target
|
||||
else:
|
||||
make_dir, make_target = \
|
||||
resolve_target_to_make(self.topobjdir,
|
||||
path_arg.relpath())
|
||||
|
||||
if make_dir is None and make_target is None:
|
||||
return 1
|
||||
|
||||
# See bug 886162 - we don't want to "accidentally" build
|
||||
# the entire tree (if that's really the intent, it's
|
||||
# unlikely they would have specified a directory.)
|
||||
if not make_dir and not make_target:
|
||||
print("The specified directory doesn't contain a "
|
||||
"Makefile and the first parent with one is the "
|
||||
"root of the tree. Please specify a directory "
|
||||
"with a Makefile or run |mach build| if you "
|
||||
"want to build the entire tree.")
|
||||
return 1
|
||||
|
||||
target_pairs.append((make_dir, make_target))
|
||||
|
||||
# Possibly add extra make depencies using dumbmake.
|
||||
if not disable_extra_make_dependencies:
|
||||
from dumbmake.dumbmake import (dependency_map,
|
||||
add_extra_dependencies)
|
||||
depfile = os.path.join(self.topsrcdir, 'build',
|
||||
'dumbmake-dependencies')
|
||||
with open(depfile) as f:
|
||||
dm = dependency_map(f.readlines())
|
||||
new_pairs = list(add_extra_dependencies(target_pairs, dm))
|
||||
self.log(logging.DEBUG, 'dumbmake',
|
||||
{'target_pairs': target_pairs,
|
||||
'new_pairs': new_pairs},
|
||||
'Added extra dependencies: will build {new_pairs} ' +
|
||||
'instead of {target_pairs}.')
|
||||
target_pairs = new_pairs
|
||||
|
||||
# Ensure build backend is up to date. The alternative is to
|
||||
# have rules in the invoked Makefile to rebuild the build
|
||||
# backend. But that involves make reinvoking itself and there
|
||||
# are undesired side-effects of this. See bug 877308 for a
|
||||
# comprehensive history lesson.
|
||||
self._run_make(directory=self.topobjdir, target='backend',
|
||||
line_handler=output.on_line, log=False,
|
||||
print_directory=False, keep_going=keep_going)
|
||||
|
||||
# Build target pairs.
|
||||
for make_dir, make_target in target_pairs:
|
||||
# We don't display build status messages during partial
|
||||
# tree builds because they aren't reliable there. This
|
||||
# could potentially be fixed if the build monitor were more
|
||||
# intelligent about encountering undefined state.
|
||||
status = self._run_make(directory=make_dir, target=make_target,
|
||||
line_handler=output.on_line, log=False, print_directory=False,
|
||||
ensure_exit_code=False, num_jobs=jobs, silent=not verbose,
|
||||
append_env={b'NO_BUILDSTATUS_MESSAGES': b'1'},
|
||||
keep_going=keep_going)
|
||||
|
||||
if status != 0:
|
||||
break
|
||||
else:
|
||||
# Try to call the default backend's build() method. This will
|
||||
# run configure to determine BUILD_BACKENDS if it hasn't run
|
||||
# yet.
|
||||
config = None
|
||||
try:
|
||||
config = self.config_environment
|
||||
except Exception:
|
||||
config_rc = self.configure(buildstatus_messages=True,
|
||||
line_handler=output.on_line)
|
||||
if config_rc != 0:
|
||||
return config_rc
|
||||
|
||||
# Even if configure runs successfully, we may have trouble
|
||||
# getting the config_environment for some builds, such as
|
||||
# OSX Universal builds. These have to go through client.mk
|
||||
# regardless.
|
||||
try:
|
||||
config = self.config_environment
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if config:
|
||||
active_backend = config.substs.get('BUILD_BACKENDS', [None])[0]
|
||||
if active_backend:
|
||||
backend_cls = get_backend_class(active_backend)(config)
|
||||
status = backend_cls.build(self, output, jobs, verbose)
|
||||
|
||||
# If the backend doesn't specify a build() method, then just
|
||||
# call client.mk directly.
|
||||
if status is None:
|
||||
status = self._run_make(srcdir=True, filename='client.mk',
|
||||
line_handler=output.on_line, log=False, print_directory=False,
|
||||
allow_parallel=False, ensure_exit_code=False, num_jobs=jobs,
|
||||
silent=not verbose, keep_going=keep_going)
|
||||
|
||||
self.log(logging.WARNING, 'warning_summary',
|
||||
{'count': len(monitor.warnings_database)},
|
||||
'{count} compiler warnings present.')
|
||||
|
||||
monitor.finish(record_usage=status == 0)
|
||||
|
||||
# Print the collected compiler warnings. This is redundant with
|
||||
# inline output from the compiler itself. However, unlike inline
|
||||
# output, this list is sorted and grouped by file, making it
|
||||
# easier to triage output.
|
||||
#
|
||||
# Only do this if we had a successful build. If the build failed,
|
||||
# there are more important things in the log to look for than
|
||||
# whatever code we warned about.
|
||||
if not status:
|
||||
# Suppress warnings for 3rd party projects in local builds
|
||||
# until we suppress them for real.
|
||||
# TODO remove entries/feature once we stop generating warnings
|
||||
# in these directories.
|
||||
pathToThirdparty = os.path.join(self.topsrcdir,
|
||||
"tools",
|
||||
"rewriting",
|
||||
"ThirdPartyPaths.txt")
|
||||
|
||||
if os.path.exists(pathToThirdparty):
|
||||
with open(pathToThirdparty) as f:
|
||||
# Normalize the path (no trailing /)
|
||||
LOCAL_SUPPRESS_DIRS = tuple(d.rstrip('/') for d in f.read().splitlines())
|
||||
else:
|
||||
# For application based on gecko like thunderbird
|
||||
LOCAL_SUPPRESS_DIRS = ()
|
||||
|
||||
suppressed_by_dir = Counter()
|
||||
|
||||
for warning in sorted(monitor.instance_warnings):
|
||||
path = mozpath.normsep(warning['filename'])
|
||||
if path.startswith(self.topsrcdir):
|
||||
path = path[len(self.topsrcdir) + 1:]
|
||||
|
||||
warning['normpath'] = path
|
||||
|
||||
if (path.startswith(LOCAL_SUPPRESS_DIRS) and
|
||||
'MOZ_AUTOMATION' not in os.environ):
|
||||
for d in LOCAL_SUPPRESS_DIRS:
|
||||
if path.startswith(d):
|
||||
suppressed_by_dir[d] += 1
|
||||
break
|
||||
|
||||
continue
|
||||
|
||||
if warning['column'] is not None:
|
||||
self.log(logging.WARNING, 'compiler_warning', warning,
|
||||
'warning: {normpath}:{line}:{column} [{flag}] '
|
||||
'{message}')
|
||||
else:
|
||||
self.log(logging.WARNING, 'compiler_warning', warning,
|
||||
'warning: {normpath}:{line} [{flag}] {message}')
|
||||
|
||||
for d, count in sorted(suppressed_by_dir.items()):
|
||||
self.log(logging.WARNING, 'suppressed_warning',
|
||||
{'dir': d, 'count': count},
|
||||
'(suppressed {count} warnings in {dir})')
|
||||
|
||||
high_finder, finder_percent = monitor.have_high_finder_usage()
|
||||
if high_finder:
|
||||
print(FINDER_SLOW_MESSAGE % finder_percent)
|
||||
|
||||
ccache_end = monitor.ccache_stats()
|
||||
|
||||
ccache_diff = None
|
||||
if ccache_start and ccache_end:
|
||||
ccache_diff = ccache_end - ccache_start
|
||||
if ccache_diff:
|
||||
self.log(logging.INFO, 'ccache',
|
||||
{'msg': ccache_diff.hit_rate_message()}, "{msg}")
|
||||
|
||||
notify_minimum_time = 300
|
||||
try:
|
||||
notify_minimum_time = int(os.environ.get('MACH_NOTIFY_MINTIME', '300'))
|
||||
except ValueError:
|
||||
# Just stick with the default
|
||||
pass
|
||||
|
||||
if monitor.elapsed > notify_minimum_time:
|
||||
# Display a notification when the build completes.
|
||||
self.notify('Build complete' if not status else 'Build failed')
|
||||
|
||||
if status:
|
||||
return status
|
||||
|
||||
long_build = monitor.elapsed > 600
|
||||
|
||||
if long_build:
|
||||
output.on_line('We know it took a while, but your build finally finished successfully!')
|
||||
else:
|
||||
output.on_line('Your build was successful!')
|
||||
|
||||
if monitor.have_resource_usage:
|
||||
excessive, swap_in, swap_out = monitor.have_excessive_swapping()
|
||||
# if excessive:
|
||||
# print(EXCESSIVE_SWAP_MESSAGE)
|
||||
|
||||
print('To view resource usage of the build, run |mach '
|
||||
'resource-usage|.')
|
||||
|
||||
telemetry_handler = getattr(mach_context,
|
||||
'telemetry_handler', None)
|
||||
telemetry_data = monitor.get_resource_usage()
|
||||
|
||||
# Record build configuration data. For now, we cherry pick
|
||||
# items we need rather than grabbing everything, in order
|
||||
# to avoid accidentally disclosing PII.
|
||||
telemetry_data['substs'] = {}
|
||||
try:
|
||||
for key in ['MOZ_ARTIFACT_BUILDS', 'MOZ_USING_CCACHE', 'MOZ_USING_SCCACHE']:
|
||||
value = self.substs.get(key, False)
|
||||
telemetry_data['substs'][key] = value
|
||||
except BuildEnvironmentNotFoundException:
|
||||
pass
|
||||
|
||||
# Grab ccache stats if available. We need to be careful not
|
||||
# to capture information that can potentially identify the
|
||||
# user (such as the cache location)
|
||||
if ccache_diff:
|
||||
telemetry_data['ccache'] = {}
|
||||
for key in [key[0] for key in ccache_diff.STATS_KEYS]:
|
||||
try:
|
||||
telemetry_data['ccache'][key] = ccache_diff._values[key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if telemetry_handler:
|
||||
telemetry_handler(mach_context, telemetry_data)
|
||||
|
||||
# Only for full builds because incremental builders likely don't
|
||||
# need to be burdened with this.
|
||||
if not what:
|
||||
try:
|
||||
# Fennec doesn't have useful output from just building. We should
|
||||
# arguably make the build action useful for Fennec. Another day...
|
||||
if self.substs['MOZ_BUILD_APP'] != 'mobile/android':
|
||||
print('To take your build for a test drive, run: |mach run|')
|
||||
app = self.substs['MOZ_BUILD_APP']
|
||||
if app in ('browser', 'mobile/android'):
|
||||
print('For more information on what to do now, see '
|
||||
'https://developer.mozilla.org/docs/Developer_Guide/So_You_Just_Built_Firefox')
|
||||
except Exception:
|
||||
# Ignore Exceptions in case we can't find config.status (such
|
||||
# as when doing OSX Universal builds)
|
||||
pass
|
||||
|
||||
return status
|
||||
|
||||
def configure(self, options=None, buildstatus_messages=False,
|
||||
line_handler=None):
|
||||
def on_line(line):
|
||||
self.log(logging.INFO, 'build_output', {'line': line}, '{line}')
|
||||
|
||||
line_handler = line_handler or on_line
|
||||
|
||||
options = ' '.join(shell_quote(o) for o in options or ())
|
||||
append_env = {b'CONFIGURE_ARGS': options.encode('utf-8')}
|
||||
|
||||
# Only print build status messages when we have an active
|
||||
# monitor.
|
||||
if not buildstatus_messages:
|
||||
append_env[b'NO_BUILDSTATUS_MESSAGES'] = b'1'
|
||||
status = self._run_make(srcdir=True, filename='client.mk',
|
||||
target='configure', line_handler=line_handler, log=False,
|
||||
print_directory=False, allow_parallel=False, ensure_exit_code=False,
|
||||
append_env=append_env)
|
||||
|
||||
if not status:
|
||||
print('Configure complete!')
|
||||
print('Be sure to run |mach build| to pick up any changes');
|
||||
|
||||
return status
|
||||
|
||||
def install_tests(self, test_objs):
|
||||
"""Install test files."""
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import errno
|
||||
import hashlib
|
||||
import itertools
|
||||
import json
|
||||
|
@ -29,26 +27,18 @@ from mach.decorators import (
|
|||
SubCommand,
|
||||
)
|
||||
|
||||
from mach.mixin.logging import LoggingMixin
|
||||
|
||||
from mach.main import Mach
|
||||
|
||||
from mozbuild.base import (
|
||||
BuildEnvironmentNotFoundException,
|
||||
MachCommandBase,
|
||||
MachCommandConditions as conditions,
|
||||
MozbuildObject,
|
||||
MozconfigFindException,
|
||||
MozconfigLoadException,
|
||||
ObjdirMismatchException,
|
||||
)
|
||||
from mozbuild.util import ensureParentDir
|
||||
|
||||
from mozbuild.backend import (
|
||||
backends,
|
||||
get_backend_class,
|
||||
)
|
||||
from mozbuild.shellutil import quote as shell_quote
|
||||
|
||||
|
||||
BUILD_WHAT_HELP = '''
|
||||
|
@ -59,19 +49,6 @@ targets as needed. BUILDING ONLY PARTS OF THE TREE CAN RESULT IN BAD TREE
|
|||
STATE. USE AT YOUR OWN RISK.
|
||||
'''.strip()
|
||||
|
||||
FINDER_SLOW_MESSAGE = '''
|
||||
===================
|
||||
PERFORMANCE WARNING
|
||||
|
||||
The OS X Finder application (file indexing used by Spotlight) used a lot of CPU
|
||||
during the build - an average of %f%% (100%% is 1 core). This made your build
|
||||
slower.
|
||||
|
||||
Consider adding ".noindex" to the end of your object directory name to have
|
||||
Finder ignore it. Or, add an indexing exclusion through the Spotlight System
|
||||
Preferences.
|
||||
===================
|
||||
'''.strip()
|
||||
|
||||
EXCESSIVE_SWAP_MESSAGE = '''
|
||||
===================
|
||||
|
@ -91,217 +68,6 @@ warning heuristic.
|
|||
'''
|
||||
|
||||
|
||||
class TerminalLoggingHandler(logging.Handler):
|
||||
"""Custom logging handler that works with terminal window dressing.
|
||||
|
||||
This class should probably live elsewhere, like the mach core. Consider
|
||||
this a proving ground for its usefulness.
|
||||
"""
|
||||
def __init__(self):
|
||||
logging.Handler.__init__(self)
|
||||
|
||||
self.fh = sys.stdout
|
||||
self.footer = None
|
||||
|
||||
def flush(self):
|
||||
self.acquire()
|
||||
|
||||
try:
|
||||
self.fh.flush()
|
||||
finally:
|
||||
self.release()
|
||||
|
||||
def emit(self, record):
|
||||
msg = self.format(record)
|
||||
|
||||
self.acquire()
|
||||
|
||||
try:
|
||||
if self.footer:
|
||||
self.footer.clear()
|
||||
|
||||
self.fh.write(msg)
|
||||
self.fh.write('\n')
|
||||
|
||||
if self.footer:
|
||||
self.footer.draw()
|
||||
|
||||
# If we don't flush, the footer may not get drawn.
|
||||
self.fh.flush()
|
||||
finally:
|
||||
self.release()
|
||||
|
||||
|
||||
class Footer(object):
|
||||
"""Handles display of a footer in a terminal.
|
||||
|
||||
This class implements the functionality common to all mach commands
|
||||
that render a footer.
|
||||
"""
|
||||
|
||||
def __init__(self, terminal):
|
||||
# terminal is a blessings.Terminal.
|
||||
self._t = terminal
|
||||
self._fh = sys.stdout
|
||||
|
||||
def clear(self):
|
||||
"""Removes the footer from the current terminal."""
|
||||
self._fh.write(self._t.move_x(0))
|
||||
self._fh.write(self._t.clear_eol())
|
||||
|
||||
def write(self, parts):
|
||||
"""Write some output in the footer, accounting for terminal width.
|
||||
|
||||
parts is a list of 2-tuples of (encoding_function, input).
|
||||
None means no encoding."""
|
||||
|
||||
# We don't want to write more characters than the current width of the
|
||||
# terminal otherwise wrapping may result in weird behavior. We can't
|
||||
# simply truncate the line at terminal width characters because a)
|
||||
# non-viewable escape characters count towards the limit and b) we
|
||||
# don't want to truncate in the middle of an escape sequence because
|
||||
# subsequent output would inherit the escape sequence.
|
||||
max_width = self._t.width
|
||||
written = 0
|
||||
write_pieces = []
|
||||
for part in parts:
|
||||
try:
|
||||
func, part = part
|
||||
encoded = getattr(self._t, func)(part)
|
||||
except ValueError:
|
||||
encoded = part
|
||||
|
||||
len_part = len(part)
|
||||
len_spaces = len(write_pieces)
|
||||
if written + len_part + len_spaces > max_width:
|
||||
write_pieces.append(part[0:max_width - written - len_spaces])
|
||||
written += len_part
|
||||
break
|
||||
|
||||
write_pieces.append(encoded)
|
||||
written += len_part
|
||||
|
||||
with self._t.location():
|
||||
self._t.move(self._t.height-1,0)
|
||||
self._fh.write(' '.join(write_pieces))
|
||||
|
||||
|
||||
class BuildProgressFooter(Footer):
|
||||
"""Handles display of a build progress indicator in a terminal.
|
||||
|
||||
When mach builds inside a blessings-supported terminal, it will render
|
||||
progress information collected from a BuildMonitor. This class converts the
|
||||
state of BuildMonitor into terminal output.
|
||||
"""
|
||||
|
||||
def __init__(self, terminal, monitor):
|
||||
Footer.__init__(self, terminal)
|
||||
self.tiers = monitor.tiers.tier_status.viewitems()
|
||||
|
||||
def draw(self):
|
||||
"""Draws this footer in the terminal."""
|
||||
|
||||
if not self.tiers:
|
||||
return
|
||||
|
||||
# The drawn terminal looks something like:
|
||||
# TIER: static export libs tools
|
||||
|
||||
parts = [('bold', 'TIER:')]
|
||||
append = parts.append
|
||||
for tier, status in self.tiers:
|
||||
if status is None:
|
||||
append(tier)
|
||||
elif status == 'finished':
|
||||
append(('green', tier))
|
||||
else:
|
||||
append(('underline_yellow', tier))
|
||||
|
||||
self.write(parts)
|
||||
|
||||
|
||||
class OutputManager(LoggingMixin):
|
||||
"""Handles writing job output to a terminal or log."""
|
||||
|
||||
def __init__(self, log_manager, footer):
|
||||
self.populate_logger()
|
||||
|
||||
self.footer = None
|
||||
terminal = log_manager.terminal
|
||||
|
||||
# TODO convert terminal footer to config file setting.
|
||||
if not terminal or os.environ.get('MACH_NO_TERMINAL_FOOTER', None):
|
||||
return
|
||||
if os.environ.get('INSIDE_EMACS', None):
|
||||
return
|
||||
|
||||
self.t = terminal
|
||||
self.footer = footer
|
||||
|
||||
self._handler = TerminalLoggingHandler()
|
||||
self._handler.setFormatter(log_manager.terminal_formatter)
|
||||
self._handler.footer = self.footer
|
||||
|
||||
old = log_manager.replace_terminal_handler(self._handler)
|
||||
self._handler.level = old.level
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if self.footer:
|
||||
self.footer.clear()
|
||||
# Prevents the footer from being redrawn if logging occurs.
|
||||
self._handler.footer = None
|
||||
|
||||
def write_line(self, line):
|
||||
if self.footer:
|
||||
self.footer.clear()
|
||||
|
||||
print(line)
|
||||
|
||||
if self.footer:
|
||||
self.footer.draw()
|
||||
|
||||
def refresh(self):
|
||||
if not self.footer:
|
||||
return
|
||||
|
||||
self.footer.clear()
|
||||
self.footer.draw()
|
||||
|
||||
class BuildOutputManager(OutputManager):
|
||||
"""Handles writing build output to a terminal, to logs, etc."""
|
||||
|
||||
def __init__(self, log_manager, monitor, footer):
|
||||
self.monitor = monitor
|
||||
OutputManager.__init__(self, log_manager, footer)
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
OutputManager.__exit__(self, exc_type, exc_value, traceback)
|
||||
|
||||
# Ensure the resource monitor is stopped because leaving it running
|
||||
# could result in the process hanging on exit because the resource
|
||||
# collection child process hasn't been told to stop.
|
||||
self.monitor.stop_resource_recording()
|
||||
|
||||
|
||||
def on_line(self, line):
|
||||
warning, state_changed, relevant = self.monitor.on_line(line)
|
||||
|
||||
if relevant:
|
||||
self.log(logging.INFO, 'build_output', {'line': line}, '{line}')
|
||||
elif state_changed:
|
||||
have_handler = hasattr(self, 'handler')
|
||||
if have_handler:
|
||||
self.handler.acquire()
|
||||
try:
|
||||
self.refresh()
|
||||
finally:
|
||||
if have_handler:
|
||||
self.handler.release()
|
||||
|
||||
|
||||
class StoreDebugParamsAndWarnAction(argparse.Action):
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
sys.stderr.write('The --debugparams argument is deprecated. Please ' +
|
||||
|
@ -387,331 +153,38 @@ class Build(MachCommandBase):
|
|||
there are build actions not captured by either. If things don't appear to
|
||||
be rebuilding, perform a vanilla `mach build` to rebuild the world.
|
||||
"""
|
||||
import which
|
||||
from mozbuild.controller.building import BuildMonitor
|
||||
from mozbuild.util import (
|
||||
mkdir,
|
||||
resolve_target_to_make,
|
||||
from mozbuild.controller.building import (
|
||||
BuildDriver,
|
||||
)
|
||||
|
||||
self.log_manager.register_structured_logger(logging.getLogger('mozbuild'))
|
||||
self.log_manager.enable_all_structured_loggers()
|
||||
|
||||
warnings_path = self._get_state_filename('warnings.json')
|
||||
monitor = self._spawn(BuildMonitor)
|
||||
monitor.init(warnings_path)
|
||||
ccache_start = monitor.ccache_stats()
|
||||
footer = BuildProgressFooter(self.log_manager.terminal, monitor)
|
||||
|
||||
# Disable indexing in objdir because it is not necessary and can slow
|
||||
# down builds.
|
||||
mkdir(self.topobjdir, not_indexed=True)
|
||||
|
||||
with BuildOutputManager(self.log_manager, monitor, footer) as output:
|
||||
monitor.start()
|
||||
|
||||
if directory is not None and not what:
|
||||
print('Can only use -C/--directory with an explicit target '
|
||||
'name.')
|
||||
return 1
|
||||
|
||||
if directory is not None:
|
||||
disable_extra_make_dependencies=True
|
||||
directory = mozpath.normsep(directory)
|
||||
if directory.startswith('/'):
|
||||
directory = directory[1:]
|
||||
|
||||
status = None
|
||||
monitor.start_resource_recording()
|
||||
if what:
|
||||
top_make = os.path.join(self.topobjdir, 'Makefile')
|
||||
if not os.path.exists(top_make):
|
||||
print('Your tree has not been configured yet. Please run '
|
||||
'|mach build| with no arguments.')
|
||||
return 1
|
||||
|
||||
# Collect target pairs.
|
||||
target_pairs = []
|
||||
for target in what:
|
||||
path_arg = self._wrap_path_argument(target)
|
||||
|
||||
if directory is not None:
|
||||
make_dir = os.path.join(self.topobjdir, directory)
|
||||
make_target = target
|
||||
else:
|
||||
make_dir, make_target = \
|
||||
resolve_target_to_make(self.topobjdir,
|
||||
path_arg.relpath())
|
||||
|
||||
if make_dir is None and make_target is None:
|
||||
return 1
|
||||
|
||||
# See bug 886162 - we don't want to "accidentally" build
|
||||
# the entire tree (if that's really the intent, it's
|
||||
# unlikely they would have specified a directory.)
|
||||
if not make_dir and not make_target:
|
||||
print("The specified directory doesn't contain a "
|
||||
"Makefile and the first parent with one is the "
|
||||
"root of the tree. Please specify a directory "
|
||||
"with a Makefile or run |mach build| if you "
|
||||
"want to build the entire tree.")
|
||||
return 1
|
||||
|
||||
target_pairs.append((make_dir, make_target))
|
||||
|
||||
# Possibly add extra make depencies using dumbmake.
|
||||
if not disable_extra_make_dependencies:
|
||||
from dumbmake.dumbmake import (dependency_map,
|
||||
add_extra_dependencies)
|
||||
depfile = os.path.join(self.topsrcdir, 'build',
|
||||
'dumbmake-dependencies')
|
||||
with open(depfile) as f:
|
||||
dm = dependency_map(f.readlines())
|
||||
new_pairs = list(add_extra_dependencies(target_pairs, dm))
|
||||
self.log(logging.DEBUG, 'dumbmake',
|
||||
{'target_pairs': target_pairs,
|
||||
'new_pairs': new_pairs},
|
||||
'Added extra dependencies: will build {new_pairs} ' +
|
||||
'instead of {target_pairs}.')
|
||||
target_pairs = new_pairs
|
||||
|
||||
# Ensure build backend is up to date. The alternative is to
|
||||
# have rules in the invoked Makefile to rebuild the build
|
||||
# backend. But that involves make reinvoking itself and there
|
||||
# are undesired side-effects of this. See bug 877308 for a
|
||||
# comprehensive history lesson.
|
||||
self._run_make(directory=self.topobjdir, target='backend',
|
||||
line_handler=output.on_line, log=False,
|
||||
print_directory=False, keep_going=keep_going)
|
||||
|
||||
# Build target pairs.
|
||||
for make_dir, make_target in target_pairs:
|
||||
# We don't display build status messages during partial
|
||||
# tree builds because they aren't reliable there. This
|
||||
# could potentially be fixed if the build monitor were more
|
||||
# intelligent about encountering undefined state.
|
||||
status = self._run_make(directory=make_dir, target=make_target,
|
||||
line_handler=output.on_line, log=False, print_directory=False,
|
||||
ensure_exit_code=False, num_jobs=jobs, silent=not verbose,
|
||||
append_env={b'NO_BUILDSTATUS_MESSAGES': b'1'},
|
||||
keep_going=keep_going)
|
||||
|
||||
if status != 0:
|
||||
break
|
||||
else:
|
||||
# Try to call the default backend's build() method. This will
|
||||
# run configure to determine BUILD_BACKENDS if it hasn't run
|
||||
# yet.
|
||||
config = None
|
||||
try:
|
||||
config = self.config_environment
|
||||
except Exception:
|
||||
config_rc = self.configure(buildstatus_messages=True,
|
||||
line_handler=output.on_line)
|
||||
if config_rc != 0:
|
||||
return config_rc
|
||||
|
||||
# Even if configure runs successfully, we may have trouble
|
||||
# getting the config_environment for some builds, such as
|
||||
# OSX Universal builds. These have to go through client.mk
|
||||
# regardless.
|
||||
try:
|
||||
config = self.config_environment
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if config:
|
||||
active_backend = config.substs.get('BUILD_BACKENDS', [None])[0]
|
||||
if active_backend:
|
||||
backend_cls = get_backend_class(active_backend)(config)
|
||||
status = backend_cls.build(self, output, jobs, verbose)
|
||||
|
||||
# If the backend doesn't specify a build() method, then just
|
||||
# call client.mk directly.
|
||||
if status is None:
|
||||
status = self._run_make(srcdir=True, filename='client.mk',
|
||||
line_handler=output.on_line, log=False, print_directory=False,
|
||||
allow_parallel=False, ensure_exit_code=False, num_jobs=jobs,
|
||||
silent=not verbose, keep_going=keep_going)
|
||||
|
||||
self.log(logging.WARNING, 'warning_summary',
|
||||
{'count': len(monitor.warnings_database)},
|
||||
'{count} compiler warnings present.')
|
||||
|
||||
# Print the collected compiler warnings. This is redundant with
|
||||
# inline output from the compiler itself. However, unlike inline
|
||||
# output, this list is sorted and grouped by file, making it
|
||||
# easier to triage output.
|
||||
#
|
||||
# Only do this if we had a successful build. If the build failed,
|
||||
# there are more important things in the log to look for than
|
||||
# whatever code we warned about.
|
||||
if not status:
|
||||
# Suppress warnings for 3rd party projects in local builds
|
||||
# until we suppress them for real.
|
||||
# TODO remove entries/feature once we stop generating warnings
|
||||
# in these directories.
|
||||
pathToThirdparty = os.path.join(self.topsrcdir,
|
||||
"tools",
|
||||
"rewriting",
|
||||
"ThirdPartyPaths.txt")
|
||||
|
||||
if os.path.exists(pathToThirdparty):
|
||||
with open(pathToThirdparty) as f:
|
||||
# Normalize the path (no trailing /)
|
||||
LOCAL_SUPPRESS_DIRS = tuple(d.rstrip('/') for d in f.read().splitlines())
|
||||
else:
|
||||
# For application based on gecko like thunderbird
|
||||
LOCAL_SUPPRESS_DIRS = ()
|
||||
|
||||
suppressed_by_dir = collections.Counter()
|
||||
|
||||
for warning in sorted(monitor.instance_warnings):
|
||||
path = mozpath.normsep(warning['filename'])
|
||||
if path.startswith(self.topsrcdir):
|
||||
path = path[len(self.topsrcdir) + 1:]
|
||||
|
||||
warning['normpath'] = path
|
||||
|
||||
if (path.startswith(LOCAL_SUPPRESS_DIRS) and
|
||||
'MOZ_AUTOMATION' not in os.environ):
|
||||
for d in LOCAL_SUPPRESS_DIRS:
|
||||
if path.startswith(d):
|
||||
suppressed_by_dir[d] += 1
|
||||
break
|
||||
|
||||
continue
|
||||
|
||||
if warning['column'] is not None:
|
||||
self.log(logging.WARNING, 'compiler_warning', warning,
|
||||
'warning: {normpath}:{line}:{column} [{flag}] '
|
||||
'{message}')
|
||||
else:
|
||||
self.log(logging.WARNING, 'compiler_warning', warning,
|
||||
'warning: {normpath}:{line} [{flag}] {message}')
|
||||
|
||||
for d, count in sorted(suppressed_by_dir.items()):
|
||||
self.log(logging.WARNING, 'suppressed_warning',
|
||||
{'dir': d, 'count': count},
|
||||
'(suppressed {count} warnings in {dir})')
|
||||
|
||||
monitor.finish(record_usage=status==0)
|
||||
|
||||
high_finder, finder_percent = monitor.have_high_finder_usage()
|
||||
if high_finder:
|
||||
print(FINDER_SLOW_MESSAGE % finder_percent)
|
||||
|
||||
ccache_end = monitor.ccache_stats()
|
||||
|
||||
ccache_diff = None
|
||||
if ccache_start and ccache_end:
|
||||
ccache_diff = ccache_end - ccache_start
|
||||
if ccache_diff:
|
||||
self.log(logging.INFO, 'ccache',
|
||||
{'msg': ccache_diff.hit_rate_message()}, "{msg}")
|
||||
|
||||
notify_minimum_time = 300
|
||||
try:
|
||||
notify_minimum_time = int(os.environ.get('MACH_NOTIFY_MINTIME', '300'))
|
||||
except ValueError:
|
||||
# Just stick with the default
|
||||
pass
|
||||
|
||||
if monitor.elapsed > notify_minimum_time:
|
||||
# Display a notification when the build completes.
|
||||
self.notify('Build complete' if not status else 'Build failed')
|
||||
|
||||
if status:
|
||||
return status
|
||||
|
||||
long_build = monitor.elapsed > 600
|
||||
|
||||
if long_build:
|
||||
output.on_line('We know it took a while, but your build finally finished successfully!')
|
||||
else:
|
||||
output.on_line('Your build was successful!')
|
||||
|
||||
if monitor.have_resource_usage:
|
||||
excessive, swap_in, swap_out = monitor.have_excessive_swapping()
|
||||
# if excessive:
|
||||
# print(EXCESSIVE_SWAP_MESSAGE)
|
||||
|
||||
print('To view resource usage of the build, run |mach '
|
||||
'resource-usage|.')
|
||||
|
||||
telemetry_handler = getattr(self._mach_context,
|
||||
'telemetry_handler', None)
|
||||
telemetry_data = monitor.get_resource_usage()
|
||||
|
||||
# Record build configuration data. For now, we cherry pick
|
||||
# items we need rather than grabbing everything, in order
|
||||
# to avoid accidentally disclosing PII.
|
||||
telemetry_data['substs'] = {}
|
||||
try:
|
||||
for key in ['MOZ_ARTIFACT_BUILDS', 'MOZ_USING_CCACHE', 'MOZ_USING_SCCACHE']:
|
||||
value = self.substs.get(key, False)
|
||||
telemetry_data['substs'][key] = value
|
||||
except BuildEnvironmentNotFoundException:
|
||||
pass
|
||||
|
||||
# Grab ccache stats if available. We need to be careful not
|
||||
# to capture information that can potentially identify the
|
||||
# user (such as the cache location)
|
||||
if ccache_diff:
|
||||
telemetry_data['ccache'] = {}
|
||||
for key in [key[0] for key in ccache_diff.STATS_KEYS]:
|
||||
try:
|
||||
telemetry_data['ccache'][key] = ccache_diff._values[key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
telemetry_handler(self._mach_context, telemetry_data)
|
||||
|
||||
# Only for full builds because incremental builders likely don't
|
||||
# need to be burdened with this.
|
||||
if not what:
|
||||
try:
|
||||
# Fennec doesn't have useful output from just building. We should
|
||||
# arguably make the build action useful for Fennec. Another day...
|
||||
if self.substs['MOZ_BUILD_APP'] != 'mobile/android':
|
||||
print('To take your build for a test drive, run: |mach run|')
|
||||
app = self.substs['MOZ_BUILD_APP']
|
||||
if app in ('browser', 'mobile/android'):
|
||||
print('For more information on what to do now, see '
|
||||
'https://developer.mozilla.org/docs/Developer_Guide/So_You_Just_Built_Firefox')
|
||||
except Exception:
|
||||
# Ignore Exceptions in case we can't find config.status (such
|
||||
# as when doing OSX Universal builds)
|
||||
pass
|
||||
|
||||
return status
|
||||
driver = self._spawn(BuildDriver)
|
||||
return driver.build(
|
||||
what=what,
|
||||
disable_extra_make_dependencies=disable_extra_make_dependencies,
|
||||
jobs=jobs,
|
||||
directory=directory,
|
||||
verbose=verbose,
|
||||
keep_going=keep_going,
|
||||
mach_context=self._mach_context)
|
||||
|
||||
@Command('configure', category='build',
|
||||
description='Configure the tree (run configure and config.status).')
|
||||
@CommandArgument('options', default=None, nargs=argparse.REMAINDER,
|
||||
help='Configure options')
|
||||
def configure(self, options=None, buildstatus_messages=False, line_handler=None):
|
||||
def on_line(line):
|
||||
self.log(logging.INFO, 'build_output', {'line': line}, '{line}')
|
||||
from mozbuild.controller.building import (
|
||||
BuildDriver,
|
||||
)
|
||||
|
||||
line_handler = line_handler or on_line
|
||||
self.log_manager.enable_all_structured_loggers()
|
||||
driver = self._spawn(BuildDriver)
|
||||
|
||||
options = ' '.join(shell_quote(o) for o in options or ())
|
||||
append_env = {b'CONFIGURE_ARGS': options.encode('utf-8')}
|
||||
|
||||
# Only print build status messages when we have an active
|
||||
# monitor.
|
||||
if not buildstatus_messages:
|
||||
append_env[b'NO_BUILDSTATUS_MESSAGES'] = b'1'
|
||||
status = self._run_make(srcdir=True, filename='client.mk',
|
||||
target='configure', line_handler=line_handler, log=False,
|
||||
print_directory=False, allow_parallel=False, ensure_exit_code=False,
|
||||
append_env=append_env)
|
||||
|
||||
if not status:
|
||||
print('Configure complete!')
|
||||
print('Be sure to run |mach build| to pick up any changes');
|
||||
|
||||
return status
|
||||
return driver.configure(
|
||||
options=options,
|
||||
buildstatus_messages=buildstatus_messages,
|
||||
line_handler=line_handler)
|
||||
|
||||
@Command('resource-usage', category='post-build',
|
||||
description='Show information about system resource usage for a build.')
|
||||
|
@ -2121,62 +1594,6 @@ class StaticAnalysisMonitor(object):
|
|||
return (warning, True)
|
||||
|
||||
|
||||
class StaticAnalysisFooter(Footer):
|
||||
"""Handles display of a static analysis progress indicator in a terminal.
|
||||
"""
|
||||
|
||||
def __init__(self, terminal, monitor):
|
||||
Footer.__init__(self, terminal)
|
||||
self.monitor = monitor
|
||||
|
||||
def draw(self):
|
||||
"""Draws this footer in the terminal."""
|
||||
|
||||
monitor = self.monitor
|
||||
total = monitor.num_files
|
||||
processed = monitor.num_files_processed
|
||||
percent = '(%.2f%%)' % (processed * 100.0 / total)
|
||||
parts = [
|
||||
('dim', 'Processing'),
|
||||
('yellow', str(processed)),
|
||||
('dim', 'of'),
|
||||
('yellow', str(total)),
|
||||
('dim', 'files'),
|
||||
('green', percent)
|
||||
]
|
||||
if monitor.current_file:
|
||||
parts.append(('bold', monitor.current_file))
|
||||
|
||||
self.write(parts)
|
||||
|
||||
|
||||
class StaticAnalysisOutputManager(OutputManager):
|
||||
"""Handles writing static analysis output to a terminal."""
|
||||
|
||||
def __init__(self, log_manager, monitor, footer):
|
||||
self.monitor = monitor
|
||||
OutputManager.__init__(self, log_manager, footer)
|
||||
|
||||
def on_line(self, line):
|
||||
warning, relevant = self.monitor.on_line(line)
|
||||
|
||||
if warning:
|
||||
self.log(logging.INFO, 'compiler_warning', warning,
|
||||
'Warning: {flag} in {filename}: {message}')
|
||||
|
||||
if relevant:
|
||||
self.log(logging.INFO, 'build_output', {'line': line}, '{line}')
|
||||
else:
|
||||
have_handler = hasattr(self, 'handler')
|
||||
if have_handler:
|
||||
self.handler.acquire()
|
||||
try:
|
||||
self.refresh()
|
||||
finally:
|
||||
if have_handler:
|
||||
self.handler.release()
|
||||
|
||||
|
||||
@CommandProvider
|
||||
class StaticAnalysis(MachCommandBase):
|
||||
"""Utilities for running C++ static analysis checks."""
|
||||
|
@ -2213,7 +1630,14 @@ class StaticAnalysis(MachCommandBase):
|
|||
'of each translation unit are always displayed')
|
||||
def check(self, source=None, jobs=2, strip=1, verbose=False,
|
||||
checks='-*', fix=False, header_filter=''):
|
||||
from mozbuild.controller.building import (
|
||||
StaticAnalysisFooter,
|
||||
StaticAnalysisOutputManager,
|
||||
)
|
||||
|
||||
self._set_log_level(verbose)
|
||||
self.log_manager.enable_all_structured_loggers()
|
||||
|
||||
rc = self._build_compile_db(verbose=verbose)
|
||||
if rc != 0:
|
||||
return rc
|
||||
|
@ -2246,8 +1670,6 @@ class StaticAnalysis(MachCommandBase):
|
|||
if fix:
|
||||
common_args.append('-fix')
|
||||
|
||||
self.log_manager.register_structured_logger(logging.getLogger('mozbuild'))
|
||||
|
||||
compile_db = json.loads(open(self._compile_db, 'r').read())
|
||||
total = 0
|
||||
import re
|
||||
|
|
|
@ -31,6 +31,10 @@ function add_ocsp_test(aHost, aExpectedResult, aOCSPResponseToServe,
|
|||
do_get_profile();
|
||||
Services.prefs.setBoolPref("security.ssl.enable_ocsp_stapling", true);
|
||||
Services.prefs.setIntPref("security.OCSP.enabled", 1);
|
||||
// Sometimes this test will fail on android due to an OCSP request timing out.
|
||||
// That aspect of OCSP requests is not what we're testing here, so we can just
|
||||
// bump the timeout and hopefully avoid these failures.
|
||||
Services.prefs.setIntPref("security.OCSP.timeoutMilliseconds.soft", 5000);
|
||||
Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 4);
|
||||
var args = [["good", "default-ee", "unused", 0],
|
||||
["expiredresponse", "default-ee", "unused", 0],
|
||||
|
|
|
@ -6,11 +6,6 @@ module.exports = {
|
|||
],
|
||||
|
||||
globals: {
|
||||
// Globals specific to mozmill
|
||||
"assert": false,
|
||||
"controller": false,
|
||||
"findElement": false,
|
||||
"mozmill": false,
|
||||
// Injected into tests via tps.jsm
|
||||
"Addons": false,
|
||||
"Bookmarks": false,
|
||||
|
@ -22,7 +17,6 @@ module.exports = {
|
|||
"Passwords": false,
|
||||
"Phase": false,
|
||||
"Prefs": false,
|
||||
"RunMozmillTest": false,
|
||||
"STATE_DISABLED": false,
|
||||
"STATE_ENABLED": false,
|
||||
"Sync": false,
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource://tps/tps.jsm");
|
||||
|
||||
var setupModule = function(module) {
|
||||
module.controller = mozmill.getBrowserController();
|
||||
assert.ok(true, "SetupModule passes");
|
||||
};
|
||||
|
||||
var setupTest = function(module) {
|
||||
assert.ok(true, "SetupTest passes");
|
||||
};
|
||||
|
||||
var testTestStep = function() {
|
||||
assert.ok(true, "test Passes");
|
||||
controller.open("http://www.mozilla.org");
|
||||
|
||||
TPS.Login();
|
||||
TPS.Sync(ACTIONS.ACTION_SYNC_WIPE_CLIENT);
|
||||
};
|
||||
|
||||
var teardownTest = function() {
|
||||
assert.ok(true, "teardownTest passes");
|
||||
};
|
||||
|
||||
var teardownModule = function() {
|
||||
assert.ok(true, "teardownModule passes");
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var setupModule = function(module) {
|
||||
module.controller = mozmill.getBrowserController();
|
||||
};
|
||||
|
||||
var testGetNode = function() {
|
||||
controller.open("about:support");
|
||||
controller.waitForPageLoad();
|
||||
|
||||
var appbox = findElement.ID(controller.tabs.activeTab, "application-box");
|
||||
assert.waitFor(() => appbox.getNode().textContent == "Firefox", "correct app name");
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* The list of phases mapped to their corresponding profiles. The object
|
||||
* here must be in strict JSON format, as it will get parsed by the Python
|
||||
* testrunner (no single quotes, extra comma's, etc).
|
||||
*/
|
||||
|
||||
var phases = { "phase1": "profile1",
|
||||
"phase2": "profile2" };
|
||||
|
||||
/*
|
||||
* Test phases
|
||||
*/
|
||||
|
||||
Phase("phase1", [
|
||||
[RunMozmillTest, "mozmill_sanity.js"],
|
||||
]);
|
||||
|
||||
Phase("phase2", [
|
||||
[Sync],
|
||||
[RunMozmillTest, "mozmill_sanity2.js"],
|
||||
]);
|
|
@ -166,7 +166,7 @@ add_task(async function test_repairs_skip_if_cant_vaidate() {
|
|||
};
|
||||
let requestor = {
|
||||
async startRepairs(validationInfo, flowID) {
|
||||
assert.ok(false, "Never should start repairs");
|
||||
ok(false, "Never should start repairs");
|
||||
},
|
||||
tryServerOnlyRepairs() {
|
||||
return false;
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
resource mozmill resource/
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>mozmill@mozilla.com</em:id>
|
||||
<em:name>Mozmill</em:name>
|
||||
<em:version>2.0.8</em:version>
|
||||
<em:description>UI Automation tool for Mozilla applications</em:description>
|
||||
<em:unpack>true</em:unpack>
|
||||
|
||||
<em:creator>Mozilla Automation and Testing Team</em:creator>
|
||||
<em:contributor>Adam Christian</em:contributor>
|
||||
<em:contributor>Mikeal Rogers</em:contributor>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>toolkit@mozilla.org</em:id>
|
||||
<em:minVersion>10.0</em:minVersion>
|
||||
<em:maxVersion>38.*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:multiprocessCompatible>true</em:multiprocessCompatible>
|
||||
</Description>
|
||||
</RDF>
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,537 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
|
||||
"Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib",
|
||||
];
|
||||
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
var Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
|
||||
var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings);
|
||||
var arrays = {}; Cu.import('resource://mozmill/stdlib/arrays.js', arrays);
|
||||
var json2 = {}; Cu.import('resource://mozmill/stdlib/json2.js', json2);
|
||||
var withs = {}; Cu.import('resource://mozmill/stdlib/withs.js', withs);
|
||||
var dom = {}; Cu.import('resource://mozmill/stdlib/dom.js', dom);
|
||||
var objects = {}; Cu.import('resource://mozmill/stdlib/objects.js', objects);
|
||||
|
||||
var countQuotes = function (str) {
|
||||
var count = 0;
|
||||
var i = 0;
|
||||
|
||||
while (i < str.length) {
|
||||
i = str.indexOf('"', i);
|
||||
if (i != -1) {
|
||||
count++;
|
||||
i++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
};
|
||||
|
||||
/**
|
||||
* smartSplit()
|
||||
*
|
||||
* Takes a lookup string as input and returns
|
||||
* a list of each node in the string
|
||||
*/
|
||||
var smartSplit = function (str) {
|
||||
// Ensure we have an even number of quotes
|
||||
if (countQuotes(str) % 2 != 0) {
|
||||
throw new Error ("Invalid Lookup Expression");
|
||||
}
|
||||
|
||||
/**
|
||||
* This regex matches a single "node" in a lookup string.
|
||||
* In otherwords, it matches the part between the two '/'s
|
||||
*
|
||||
* Regex Explanation:
|
||||
* \/ - start matching at the first forward slash
|
||||
* ([^\/"]*"[^"]*")* - match as many pairs of quotes as possible until we hit a slash (ignore slashes inside quotes)
|
||||
* [^\/]* - match the remainder of text outside of last quote but before next slash
|
||||
*/
|
||||
var re = /\/([^\/"]*"[^"]*")*[^\/]*/g
|
||||
var ret = []
|
||||
var match = re.exec(str);
|
||||
|
||||
while (match != null) {
|
||||
ret.push(match[0].replace(/^\//, ""));
|
||||
match = re.exec(str);
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* defaultDocuments()
|
||||
*
|
||||
* Returns a list of default documents in which to search for elements
|
||||
* if no document is provided
|
||||
*/
|
||||
function defaultDocuments() {
|
||||
var win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
return [
|
||||
win.document,
|
||||
utils.getBrowserObject(win).selectedBrowser.contentWindow.document
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* nodeSearch()
|
||||
*
|
||||
* Takes an optional document, callback and locator string
|
||||
* Returns a handle to the located element or null
|
||||
*/
|
||||
function nodeSearch(doc, func, string) {
|
||||
if (doc != undefined) {
|
||||
var documents = [doc];
|
||||
} else {
|
||||
var documents = defaultDocuments();
|
||||
}
|
||||
|
||||
var e = null;
|
||||
var element = null;
|
||||
|
||||
//inline function to recursively find the element in the DOM, cross frame.
|
||||
var search = function (win, func, string) {
|
||||
if (win == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//do the lookup in the current window
|
||||
element = func.call(win, string);
|
||||
|
||||
if (!element || (element.length == 0)) {
|
||||
var frames = win.frames;
|
||||
for (var i = 0; i < frames.length; i++) {
|
||||
search(frames[i], func, string);
|
||||
}
|
||||
} else {
|
||||
e = element;
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < documents.length; ++i) {
|
||||
var win = documents[i].defaultView;
|
||||
search(win, func, string);
|
||||
if (e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return e;
|
||||
};
|
||||
|
||||
/**
|
||||
* Selector()
|
||||
*
|
||||
* Finds an element by selector string
|
||||
*/
|
||||
function Selector(_document, selector, index) {
|
||||
if (selector == undefined) {
|
||||
throw new Error('Selector constructor did not recieve enough arguments.');
|
||||
}
|
||||
|
||||
this.selector = selector;
|
||||
|
||||
this.getNodeForDocument = function (s) {
|
||||
return this.document.querySelectorAll(s);
|
||||
};
|
||||
|
||||
var nodes = nodeSearch(_document, this.getNodeForDocument, this.selector);
|
||||
|
||||
return nodes ? nodes[index || 0] : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* ID()
|
||||
*
|
||||
* Finds an element by ID
|
||||
*/
|
||||
function ID(_document, nodeID) {
|
||||
if (nodeID == undefined) {
|
||||
throw new Error('ID constructor did not recieve enough arguments.');
|
||||
}
|
||||
|
||||
this.getNodeForDocument = function (nodeID) {
|
||||
return this.document.getElementById(nodeID);
|
||||
};
|
||||
|
||||
return nodeSearch(_document, this.getNodeForDocument, nodeID);
|
||||
};
|
||||
|
||||
/**
|
||||
* Link()
|
||||
*
|
||||
* Finds a link by innerHTML
|
||||
*/
|
||||
function Link(_document, linkName) {
|
||||
if (linkName == undefined) {
|
||||
throw new Error('Link constructor did not recieve enough arguments.');
|
||||
}
|
||||
|
||||
this.getNodeForDocument = function (linkName) {
|
||||
var getText = function (el) {
|
||||
var text = "";
|
||||
|
||||
if (el.nodeType == 3) { //textNode
|
||||
if (el.data != undefined) {
|
||||
text = el.data;
|
||||
} else {
|
||||
text = el.innerHTML;
|
||||
}
|
||||
|
||||
text = text.replace(/n|r|t/g, " ");
|
||||
}
|
||||
else if (el.nodeType == 1) { //elementNode
|
||||
for (var i = 0; i < el.childNodes.length; i++) {
|
||||
var child = el.childNodes.item(i);
|
||||
text += getText(child);
|
||||
}
|
||||
|
||||
if (el.tagName == "P" || el.tagName == "BR" ||
|
||||
el.tagName == "HR" || el.tagName == "DIV") {
|
||||
text += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
//sometimes the windows won't have this function
|
||||
try {
|
||||
var links = this.document.getElementsByTagName('a');
|
||||
} catch (e) {
|
||||
// ADD LOG LINE mresults.write('Error: '+ e, 'lightred');
|
||||
}
|
||||
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var el = links[i];
|
||||
//if (getText(el).indexOf(this.linkName) != -1) {
|
||||
if (el.innerHTML.indexOf(linkName) != -1) {
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return nodeSearch(_document, this.getNodeForDocument, linkName);
|
||||
};
|
||||
|
||||
/**
|
||||
* XPath()
|
||||
*
|
||||
* Finds an element by XPath
|
||||
*/
|
||||
function XPath(_document, expr) {
|
||||
if (expr == undefined) {
|
||||
throw new Error('XPath constructor did not recieve enough arguments.');
|
||||
}
|
||||
|
||||
this.getNodeForDocument = function (s) {
|
||||
var aNode = this.document;
|
||||
var aExpr = s;
|
||||
var xpe = null;
|
||||
|
||||
if (this.document.defaultView == null) {
|
||||
xpe = new getMethodInWindows('XPathEvaluator')();
|
||||
} else {
|
||||
xpe = new this.document.defaultView.XPathEvaluator();
|
||||
}
|
||||
|
||||
var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement
|
||||
: aNode.ownerDocument.documentElement);
|
||||
var result = xpe.evaluate(aExpr, aNode, nsResolver, 0, null);
|
||||
var found = [];
|
||||
var res;
|
||||
|
||||
while (res = result.iterateNext()) {
|
||||
found.push(res);
|
||||
}
|
||||
|
||||
return found[0];
|
||||
};
|
||||
|
||||
return nodeSearch(_document, this.getNodeForDocument, expr);
|
||||
};
|
||||
|
||||
/**
|
||||
* Name()
|
||||
*
|
||||
* Finds an element by Name
|
||||
*/
|
||||
function Name(_document, nName) {
|
||||
if (nName == undefined) {
|
||||
throw new Error('Name constructor did not recieve enough arguments.');
|
||||
}
|
||||
|
||||
this.getNodeForDocument = function (s) {
|
||||
try{
|
||||
var els = this.document.getElementsByName(s);
|
||||
if (els.length > 0) {
|
||||
return els[0];
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return nodeSearch(_document, this.getNodeForDocument, nName);
|
||||
};
|
||||
|
||||
|
||||
var _returnResult = function (results) {
|
||||
if (results.length == 0) {
|
||||
return null
|
||||
}
|
||||
else if (results.length == 1) {
|
||||
return results[0];
|
||||
} else {
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
var _forChildren = function (element, name, value) {
|
||||
var results = [];
|
||||
var nodes = Array.from(element.childNodes).filter(e => e);
|
||||
|
||||
for (var i in nodes) {
|
||||
var n = nodes[i];
|
||||
if (n[name] == value) {
|
||||
results.push(n);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
var _forAnonChildren = function (_document, element, name, value) {
|
||||
var results = [];
|
||||
var nodes = Array.from(_document.getAnoymousNodes(element)).filter(e => e);
|
||||
|
||||
for (var i in nodes ) {
|
||||
var n = nodes[i];
|
||||
if (n[name] == value) {
|
||||
results.push(n);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
var _byID = function (_document, parent, value) {
|
||||
return _returnResult(_forChildren(parent, 'id', value));
|
||||
}
|
||||
|
||||
var _byName = function (_document, parent, value) {
|
||||
return _returnResult(_forChildren(parent, 'tagName', value));
|
||||
}
|
||||
|
||||
var _byAttrib = function (parent, attributes) {
|
||||
var results = [];
|
||||
var nodes = parent.childNodes;
|
||||
|
||||
for (var i in nodes) {
|
||||
var n = nodes[i];
|
||||
let requirementPass = 0;
|
||||
let requirementLength = 0;
|
||||
|
||||
for (var a in attributes) {
|
||||
requirementLength++;
|
||||
try {
|
||||
if (n.getAttribute(a) == attributes[a]) {
|
||||
requirementPass++;
|
||||
}
|
||||
} catch (e) {
|
||||
// Workaround any bugs in custom attribute crap in XUL elements
|
||||
}
|
||||
}
|
||||
|
||||
if (requirementPass == requirementLength) {
|
||||
results.push(n);
|
||||
}
|
||||
}
|
||||
|
||||
return _returnResult(results)
|
||||
}
|
||||
|
||||
var _byAnonAttrib = function (_document, parent, attributes) {
|
||||
var results = [];
|
||||
|
||||
if (objects.getLength(attributes) == 1) {
|
||||
for (var i in attributes) {
|
||||
var k = i;
|
||||
var v = attributes[i];
|
||||
}
|
||||
|
||||
var result = _document.getAnonymousElementByAttribute(parent, k, v);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
var nodes = Array.from(_document.getAnonymousNodes(parent)).filter(n => n.getAttribute);
|
||||
|
||||
function resultsForNodes (nodes) {
|
||||
for (var i in nodes) {
|
||||
var n = nodes[i];
|
||||
requirementPass = 0;
|
||||
requirementLength = 0;
|
||||
|
||||
for (var a in attributes) {
|
||||
requirementLength++;
|
||||
if (n.getAttribute(a) == attributes[a]) {
|
||||
requirementPass++;
|
||||
}
|
||||
}
|
||||
|
||||
if (requirementPass == requirementLength) {
|
||||
results.push(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resultsForNodes(nodes);
|
||||
if (results.length == 0) {
|
||||
resultsForNodes(Array.from(parent.childNodes).filter(n => n != undefined && n.getAttribute));
|
||||
}
|
||||
|
||||
return _returnResult(results)
|
||||
}
|
||||
|
||||
var _byIndex = function (_document, parent, i) {
|
||||
if (parent instanceof Array) {
|
||||
return parent[i];
|
||||
}
|
||||
|
||||
return parent.childNodes[i];
|
||||
}
|
||||
|
||||
var _anonByName = function (_document, parent, value) {
|
||||
return _returnResult(_forAnonChildren(_document, parent, 'tagName', value));
|
||||
}
|
||||
|
||||
var _anonByAttrib = function (_document, parent, value) {
|
||||
return _byAnonAttrib(_document, parent, value);
|
||||
}
|
||||
|
||||
var _anonByIndex = function (_document, parent, i) {
|
||||
return _document.getAnonymousNodes(parent)[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup()
|
||||
*
|
||||
* Finds an element by Lookup expression
|
||||
*/
|
||||
function Lookup(_document, expression) {
|
||||
if (expression == undefined) {
|
||||
throw new Error('Lookup constructor did not recieve enough arguments.');
|
||||
}
|
||||
|
||||
var expSplit = smartSplit(expression).filter(e => e != '');
|
||||
expSplit.unshift(_document);
|
||||
|
||||
var nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex};
|
||||
var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex};
|
||||
|
||||
/**
|
||||
* Reduces the lookup expression
|
||||
* @param {Object} parentNode
|
||||
* Parent node (previousValue of the formerly executed reduce callback)
|
||||
* @param {String} exp
|
||||
* Lookup expression for the parents child node
|
||||
*
|
||||
* @returns {Object} Node found by the given expression
|
||||
*/
|
||||
var reduceLookup = function (parentNode, exp) {
|
||||
// Abort in case the parent node was not found
|
||||
if (!parentNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle case where only index is provided
|
||||
var cases = nCases;
|
||||
|
||||
// Handle ending index before any of the expression gets mangled
|
||||
if (withs.endsWith(exp, ']')) {
|
||||
var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']'));
|
||||
}
|
||||
|
||||
// Handle anon
|
||||
if (withs.startsWith(exp, 'anon')) {
|
||||
exp = strings.vslice(exp, '(', ')');
|
||||
cases = aCases;
|
||||
}
|
||||
|
||||
if (withs.startsWith(exp, '[')) {
|
||||
try {
|
||||
var obj = json2.JSON.parse(strings.vslice(exp, '[', ']'));
|
||||
} catch (e) {
|
||||
throw new SyntaxError(e + '. String to be parsed was || ' +
|
||||
strings.vslice(exp, '[', ']') + ' ||');
|
||||
}
|
||||
|
||||
var r = cases['index'](_document, parentNode, obj);
|
||||
if (r == null) {
|
||||
throw new SyntaxError('Expression "' + exp +
|
||||
'" returned null. Anonymous == ' + (cases == aCases));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
for (var c in cases) {
|
||||
if (withs.startsWith(exp, c)) {
|
||||
try {
|
||||
var obj = json2.JSON.parse(strings.vslice(exp, '(', ')'))
|
||||
} catch (e) {
|
||||
throw new SyntaxError(e + '. String to be parsed was || ' +
|
||||
strings.vslice(exp, '(', ')') + ' ||');
|
||||
}
|
||||
var result = cases[c](_document, parentNode, obj);
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
if (withs.startsWith(exp, '{')) {
|
||||
try {
|
||||
var obj = json2.JSON.parse(exp);
|
||||
} catch (e) {
|
||||
throw new SyntaxError(e + '. String to be parsed was || ' + exp + ' ||');
|
||||
}
|
||||
|
||||
if (cases == aCases) {
|
||||
var result = _anonByAttrib(_document, parentNode, obj);
|
||||
} else {
|
||||
var result = _byAttrib(parentNode, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Final return
|
||||
if (expIndex) {
|
||||
// TODO: Check length and raise error
|
||||
return result[expIndex];
|
||||
} else {
|
||||
// TODO: Check length and raise error
|
||||
return result;
|
||||
}
|
||||
|
||||
// Maybe we should cause an exception here
|
||||
return false;
|
||||
};
|
||||
|
||||
return expSplit.reduce(reduceLookup);
|
||||
};
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,283 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["controller", "utils", "elementslib", "os",
|
||||
"getBrowserController", "newBrowserController",
|
||||
"getAddonsController", "getPreferencesController",
|
||||
"newMail3PaneController", "getMail3PaneController",
|
||||
"wm", "platform", "getAddrbkController",
|
||||
"getMsgComposeController", "getDownloadsController",
|
||||
"Application", "findElement",
|
||||
"getPlacesController", 'isMac', 'isLinux', 'isWindows',
|
||||
"firePythonCallback", "getAddons"
|
||||
];
|
||||
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
var Cu = Components.utils;
|
||||
|
||||
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// imports
|
||||
var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
|
||||
var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
|
||||
var controller = {}; Cu.import('resource://mozmill/driver/controller.js', controller);
|
||||
var elementslib = {}; Cu.import('resource://mozmill/driver/elementslib.js', elementslib);
|
||||
var findElement = {}; Cu.import('resource://mozmill/driver/mozelement.js', findElement);
|
||||
var os = {}; Cu.import('resource://mozmill/stdlib/os.js', os);
|
||||
var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
|
||||
var windows = {}; Cu.import('resource://mozmill/modules/windows.js', windows);
|
||||
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
// This is a useful "check" timer. See utils.js, good for debugging
|
||||
if (DEBUG) {
|
||||
utils.startTimer();
|
||||
}
|
||||
|
||||
var assert = new assertions.Assert();
|
||||
|
||||
// platform information
|
||||
var platform = os.getPlatform();
|
||||
var isMac = false;
|
||||
var isWindows = false;
|
||||
var isLinux = false;
|
||||
|
||||
if (platform == "darwin"){
|
||||
isMac = true;
|
||||
}
|
||||
|
||||
if (platform == "winnt"){
|
||||
isWindows = true;
|
||||
}
|
||||
|
||||
if (platform == "linux"){
|
||||
isLinux = true;
|
||||
}
|
||||
|
||||
var wm = Services.wm;
|
||||
|
||||
var appInfo = Services.appinfo;
|
||||
var Application = utils.applicationName;
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the list with information about installed add-ons.
|
||||
*
|
||||
* @returns {String} JSON data of installed add-ons
|
||||
*/
|
||||
function getAddons() {
|
||||
var addons = null;
|
||||
|
||||
AddonManager.getAllAddons(function (addonList) {
|
||||
var tmp_list = [ ];
|
||||
|
||||
addonList.forEach(function (addon) {
|
||||
var tmp = { };
|
||||
|
||||
// We have to filter out properties of type 'function' of the addon
|
||||
// object, which will break JSON.stringify() and result in incomplete
|
||||
// addon information.
|
||||
for (var key in addon) {
|
||||
if (typeof(addon[key]) !== "function") {
|
||||
tmp[key] = addon[key];
|
||||
}
|
||||
}
|
||||
|
||||
tmp_list.push(tmp);
|
||||
});
|
||||
|
||||
addons = tmp_list;
|
||||
});
|
||||
|
||||
try {
|
||||
// Sychronize with getAllAddons so we do not return too early
|
||||
assert.waitFor(function () {
|
||||
return !!addons;
|
||||
})
|
||||
|
||||
return addons;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves application details for the Mozmill report
|
||||
*
|
||||
* @return {String} JSON data of application details
|
||||
*/
|
||||
function getApplicationDetails() {
|
||||
var locale = Services.locale.getAppLocaleAsLangTag();
|
||||
|
||||
// Put all our necessary information into JSON and return it:
|
||||
// appinfo, startupinfo, and addons
|
||||
var details = {
|
||||
application_id: appInfo.ID,
|
||||
application_name: Application,
|
||||
application_version: appInfo.version,
|
||||
application_locale: locale,
|
||||
platform_buildid: appInfo.platformBuildID,
|
||||
platform_version: appInfo.platformVersion,
|
||||
addons: getAddons(),
|
||||
startupinfo: getStartupInfo(),
|
||||
paths: {
|
||||
appdata: Services.dirsvc.get('UAppData', Ci.nsIFile).path,
|
||||
profile: Services.dirsvc.get('ProfD', Ci.nsIFile).path
|
||||
}
|
||||
};
|
||||
|
||||
return JSON.stringify(details);
|
||||
}
|
||||
|
||||
// get startup time if available
|
||||
// see http://blog.mozilla.com/tglek/2011/04/26/measuring-startup-speed-correctly/
|
||||
function getStartupInfo() {
|
||||
var startupInfo = {};
|
||||
|
||||
try {
|
||||
var _startupInfo = Services.startup.getStartupInfo();
|
||||
for (var time in _startupInfo) {
|
||||
// convert from Date object to ms since epoch
|
||||
startupInfo[time] = _startupInfo[time].getTime();
|
||||
}
|
||||
} catch (e) {
|
||||
startupInfo = null;
|
||||
}
|
||||
|
||||
return startupInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function newBrowserController () {
|
||||
return new controller.MozMillController(utils.getMethodInWindows('OpenBrowserWindow')());
|
||||
}
|
||||
|
||||
function getBrowserController () {
|
||||
var browserWindow = wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
if (browserWindow == null) {
|
||||
return newBrowserController();
|
||||
} else {
|
||||
return new controller.MozMillController(browserWindow);
|
||||
}
|
||||
}
|
||||
|
||||
function getPlacesController () {
|
||||
utils.getMethodInWindows('PlacesCommandHook').showPlacesOrganizer('AllBookmarks');
|
||||
|
||||
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
||||
}
|
||||
|
||||
function getAddonsController () {
|
||||
if (Application == 'SeaMonkey') {
|
||||
utils.getMethodInWindows('toEM')();
|
||||
}
|
||||
else if (Application == 'Thunderbird') {
|
||||
utils.getMethodInWindows('openAddonsMgr')();
|
||||
}
|
||||
else if (Application == 'Sunbird') {
|
||||
utils.getMethodInWindows('goOpenAddons')();
|
||||
} else {
|
||||
utils.getMethodInWindows('BrowserOpenAddonsMgr')();
|
||||
}
|
||||
|
||||
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
||||
}
|
||||
|
||||
function getDownloadsController() {
|
||||
utils.getMethodInWindows('BrowserDownloadsUI')();
|
||||
|
||||
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
||||
}
|
||||
|
||||
function getPreferencesController() {
|
||||
if (Application == 'Thunderbird') {
|
||||
utils.getMethodInWindows('openOptionsDialog')();
|
||||
} else {
|
||||
utils.getMethodInWindows('openPreferences')();
|
||||
}
|
||||
|
||||
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
||||
}
|
||||
|
||||
// Thunderbird functions
|
||||
function newMail3PaneController () {
|
||||
return new controller.MozMillController(utils.getMethodInWindows('toMessengerWindow')());
|
||||
}
|
||||
|
||||
function getMail3PaneController () {
|
||||
var mail3PaneWindow = wm.getMostRecentWindow("mail:3pane");
|
||||
|
||||
if (mail3PaneWindow == null) {
|
||||
return newMail3PaneController();
|
||||
} else {
|
||||
return new controller.MozMillController(mail3PaneWindow);
|
||||
}
|
||||
}
|
||||
|
||||
// Thunderbird - Address book window
|
||||
function newAddrbkController () {
|
||||
utils.getMethodInWindows("toAddressBook")();
|
||||
utils.sleep(2000);
|
||||
var addyWin = wm.getMostRecentWindow("mail:addressbook");
|
||||
|
||||
return new controller.MozMillController(addyWin);
|
||||
}
|
||||
|
||||
function getAddrbkController () {
|
||||
var addrbkWindow = wm.getMostRecentWindow("mail:addressbook");
|
||||
if (addrbkWindow == null) {
|
||||
return newAddrbkController();
|
||||
} else {
|
||||
return new controller.MozMillController(addrbkWindow);
|
||||
}
|
||||
}
|
||||
|
||||
function firePythonCallback (filename, method, args, kwargs) {
|
||||
let obj = {'filename': filename, 'method': method};
|
||||
obj['args'] = args || [];
|
||||
obj['kwargs'] = kwargs || {};
|
||||
|
||||
broker.sendMessage("firePythonCallback", obj);
|
||||
}
|
||||
|
||||
function timer (name) {
|
||||
this.name = name;
|
||||
this.timers = {};
|
||||
this.actions = [];
|
||||
|
||||
frame.timers.push(this);
|
||||
}
|
||||
|
||||
timer.prototype.start = function (name) {
|
||||
this.timers[name].startTime = (new Date).getTime();
|
||||
}
|
||||
|
||||
timer.prototype.stop = function (name) {
|
||||
var t = this.timers[name];
|
||||
|
||||
t.endTime = (new Date).getTime();
|
||||
t.totalTime = (t.endTime - t.startTime);
|
||||
}
|
||||
|
||||
timer.prototype.end = function () {
|
||||
frame.events.fireEvent("timer", this);
|
||||
frame.timers.remove(this);
|
||||
}
|
||||
|
||||
// Initialization
|
||||
|
||||
/**
|
||||
* Initialize Mozmill
|
||||
*/
|
||||
function initialize() {
|
||||
windows.init();
|
||||
}
|
||||
|
||||
initialize();
|
|
@ -1,58 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['addListener', 'addObject',
|
||||
'removeListener',
|
||||
'sendMessage', 'log', 'pass', 'fail'];
|
||||
|
||||
var listeners = {};
|
||||
|
||||
// add a listener for a specific message type
|
||||
function addListener(msgType, listener) {
|
||||
if (listeners[msgType] === undefined) {
|
||||
listeners[msgType] = [];
|
||||
}
|
||||
|
||||
listeners[msgType].push(listener);
|
||||
}
|
||||
|
||||
// add each method in an object as a message listener
|
||||
function addObject(object) {
|
||||
for (var msgType in object) {
|
||||
addListener(msgType, object[msgType]);
|
||||
}
|
||||
}
|
||||
|
||||
// remove a listener for all message types
|
||||
function removeListener(listener) {
|
||||
for (var msgType in listeners) {
|
||||
for (let i = 0; i < listeners.length; ++i) {
|
||||
if (listeners[msgType][i] == listener) {
|
||||
listeners[msgType].splice(i, 1); // remove listener from array
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessage(msgType, obj) {
|
||||
if (listeners[msgType] === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < listeners[msgType].length; ++i) {
|
||||
listeners[msgType][i](obj);
|
||||
}
|
||||
}
|
||||
|
||||
function log(obj) {
|
||||
sendMessage('log', obj);
|
||||
}
|
||||
|
||||
function pass(obj) {
|
||||
sendMessage('pass', obj);
|
||||
}
|
||||
|
||||
function fail(obj) {
|
||||
sendMessage('fail', obj);
|
||||
}
|
|
@ -1,672 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['Assert', 'Expect'];
|
||||
|
||||
var Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
|
||||
var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors);
|
||||
var stack = {}; Cu.import('resource://mozmill/modules/stack.js', stack);
|
||||
|
||||
/**
|
||||
* @name assertions
|
||||
* @namespace Defines expect and assert methods to be used for assertions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The Assert class implements fatal assertions, and can be used in cases
|
||||
* when a failing test has to directly abort the current test function. All
|
||||
* remaining tasks will not be performed.
|
||||
*
|
||||
*/
|
||||
var Assert = function () {}
|
||||
|
||||
Assert.prototype = {
|
||||
|
||||
// The following deepEquals implementation is from Narwhal under this license:
|
||||
|
||||
// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
|
||||
//
|
||||
// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!
|
||||
//
|
||||
// Originally from narwhal.js (http://narwhaljs.org)
|
||||
// Copyright (c) 2009 Thomas Robinson <280north.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the 'Software'), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
_deepEqual: function (actual, expected) {
|
||||
// 7.1. All identical values are equivalent, as determined by ===.
|
||||
if (actual === expected) {
|
||||
return true;
|
||||
|
||||
// 7.2. If the expected value is a Date object, the actual value is
|
||||
// equivalent if it is also a Date object that refers to the same time.
|
||||
} else if (actual instanceof Date && expected instanceof Date) {
|
||||
return actual.getTime() === expected.getTime();
|
||||
|
||||
// 7.3. Other pairs that do not both pass typeof value == 'object',
|
||||
// equivalence is determined by ==.
|
||||
} else if (typeof actual != 'object' && typeof expected != 'object') {
|
||||
return actual == expected;
|
||||
|
||||
// 7.4. For all other Object pairs, including Array objects, equivalence is
|
||||
// determined by having the same number of owned properties (as verified
|
||||
// with Object.prototype.hasOwnProperty.call), the same set of keys
|
||||
// (although not necessarily the same order), equivalent values for every
|
||||
// corresponding key, and an identical 'prototype' property. Note: this
|
||||
// accounts for both named and indexed properties on Arrays.
|
||||
} else {
|
||||
return this._objEquiv(actual, expected);
|
||||
}
|
||||
},
|
||||
|
||||
_objEquiv: function (a, b) {
|
||||
if (a == null || a == undefined || b == null || b == undefined)
|
||||
return false;
|
||||
// an identical 'prototype' property.
|
||||
if (a.prototype !== b.prototype) return false;
|
||||
|
||||
function isArguments(object) {
|
||||
return Object.prototype.toString.call(object) == '[object Arguments]';
|
||||
}
|
||||
|
||||
//~~~I've managed to break Object.keys through screwy arguments passing.
|
||||
// Converting to array solves the problem.
|
||||
if (isArguments(a)) {
|
||||
if (!isArguments(b)) {
|
||||
return false;
|
||||
}
|
||||
a = pSlice.call(a);
|
||||
b = pSlice.call(b);
|
||||
return this._deepEqual(a, b);
|
||||
}
|
||||
try {
|
||||
var ka = Object.keys(a),
|
||||
kb = Object.keys(b),
|
||||
key, i;
|
||||
} catch (e) {//happens when one is a string literal and the other isn't
|
||||
return false;
|
||||
}
|
||||
// having the same number of owned properties (keys incorporates
|
||||
// hasOwnProperty)
|
||||
if (ka.length != kb.length)
|
||||
return false;
|
||||
//the same set of keys (although not necessarily the same order),
|
||||
ka.sort();
|
||||
kb.sort();
|
||||
//~~~cheap key test
|
||||
for (i = ka.length - 1; i >= 0; i--) {
|
||||
if (ka[i] != kb[i])
|
||||
return false;
|
||||
}
|
||||
//equivalent values for every corresponding key, and
|
||||
//~~~possibly expensive deep test
|
||||
for (i = ka.length - 1; i >= 0; i--) {
|
||||
key = ka[i];
|
||||
if (!this._deepEqual(a[key], b[key])) return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_expectedException : function Assert__expectedException(actual, expected) {
|
||||
if (!actual || !expected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expected instanceof RegExp) {
|
||||
return expected.test(actual);
|
||||
} else if (actual instanceof expected) {
|
||||
return true;
|
||||
} else if (expected.call({}, actual) === true) {
|
||||
return true;
|
||||
} else if (actual.name === expected.name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Log a test as failing by throwing an AssertionException.
|
||||
*
|
||||
* @param {object} aResult
|
||||
* Test result details used for reporting.
|
||||
* <dl>
|
||||
* <dd>fileName</dd>
|
||||
* <dt>Name of the file in which the assertion failed.</dt>
|
||||
* <dd>functionName</dd>
|
||||
* <dt>Function in which the assertion failed.</dt>
|
||||
* <dd>lineNumber</dd>
|
||||
* <dt>Line number of the file in which the assertion failed.</dt>
|
||||
* <dd>message</dd>
|
||||
* <dt>Message why the assertion failed.</dt>
|
||||
* </dl>
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
*/
|
||||
_logFail: function Assert__logFail(aResult) {
|
||||
throw new errors.AssertionError(aResult.message,
|
||||
aResult.fileName,
|
||||
aResult.lineNumber,
|
||||
aResult.functionName,
|
||||
aResult.name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Log a test as passing by adding a pass frame.
|
||||
*
|
||||
* @param {object} aResult
|
||||
* Test result details used for reporting.
|
||||
* <dl>
|
||||
* <dd>fileName</dd>
|
||||
* <dt>Name of the file in which the assertion failed.</dt>
|
||||
* <dd>functionName</dd>
|
||||
* <dt>Function in which the assertion failed.</dt>
|
||||
* <dd>lineNumber</dd>
|
||||
* <dt>Line number of the file in which the assertion failed.</dt>
|
||||
* <dd>message</dd>
|
||||
* <dt>Message why the assertion failed.</dt>
|
||||
* </dl>
|
||||
*/
|
||||
_logPass: function Assert__logPass(aResult) {
|
||||
broker.pass({pass: aResult});
|
||||
},
|
||||
|
||||
/**
|
||||
* Test the condition and mark test as passed or failed
|
||||
*
|
||||
* @param {boolean} aCondition
|
||||
* Condition to test.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result
|
||||
* @param {string} aDiagnosis
|
||||
* Diagnose message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
_test: function Assert__test(aCondition, aMessage, aDiagnosis) {
|
||||
let diagnosis = aDiagnosis || "";
|
||||
let message = aMessage || "";
|
||||
|
||||
if (diagnosis)
|
||||
message = aMessage ? message + " - " + diagnosis : diagnosis;
|
||||
|
||||
// Build result data
|
||||
let frame = stack.findCallerFrame(Components.stack);
|
||||
|
||||
let result = {
|
||||
'fileName' : frame.filename.replace(/(.*)-> /, ""),
|
||||
'functionName' : frame.name,
|
||||
'lineNumber' : frame.lineNumber,
|
||||
'message' : message
|
||||
};
|
||||
|
||||
// Log test result
|
||||
if (aCondition) {
|
||||
this._logPass(result);
|
||||
}
|
||||
else {
|
||||
result.stack = Components.stack;
|
||||
this._logFail(result);
|
||||
}
|
||||
|
||||
return aCondition;
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform an always passing test
|
||||
*
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result.
|
||||
* @returns {boolean} Always returns true.
|
||||
*/
|
||||
pass: function Assert_pass(aMessage) {
|
||||
return this._test(true, aMessage, undefined);
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform an always failing test
|
||||
*
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result.
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Always returns false.
|
||||
*/
|
||||
fail: function Assert_fail(aMessage) {
|
||||
return this._test(false, aMessage, undefined);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if the value pass
|
||||
*
|
||||
* @param {boolean|string|number|object} aValue
|
||||
* Value to test.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result.
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
ok: function Assert_ok(aValue, aMessage) {
|
||||
let condition = !!aValue;
|
||||
let diagnosis = "got '" + aValue + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if both specified values are identical.
|
||||
*
|
||||
* @param {boolean|string|number|object} aValue
|
||||
* Value to test.
|
||||
* @param {boolean|string|number|object} aExpected
|
||||
* Value to strictly compare with.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
equal: function Assert_equal(aValue, aExpected, aMessage) {
|
||||
let condition = (aValue === aExpected);
|
||||
let diagnosis = "'" + aValue + "' should equal '" + aExpected + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if both specified values are not identical.
|
||||
*
|
||||
* @param {boolean|string|number|object} aValue
|
||||
* Value to test.
|
||||
* @param {boolean|string|number|object} aExpected
|
||||
* Value to strictly compare with.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
notEqual: function Assert_notEqual(aValue, aExpected, aMessage) {
|
||||
let condition = (aValue !== aExpected);
|
||||
let diagnosis = "'" + aValue + "' should not equal '" + aExpected + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if an object equals another object
|
||||
*
|
||||
* @param {object} aValue
|
||||
* The object to test.
|
||||
* @param {object} aExpected
|
||||
* The object to strictly compare with.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
deepEqual: function equal(aValue, aExpected, aMessage) {
|
||||
let condition = this._deepEqual(aValue, aExpected);
|
||||
try {
|
||||
var aValueString = JSON.stringify(aValue);
|
||||
} catch (e) {
|
||||
var aValueString = String(aValue);
|
||||
}
|
||||
try {
|
||||
var aExpectedString = JSON.stringify(aExpected);
|
||||
} catch (e) {
|
||||
var aExpectedString = String(aExpected);
|
||||
}
|
||||
|
||||
let diagnosis = "'" + aValueString + "' should equal '" +
|
||||
aExpectedString + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if an object does not equal another object
|
||||
*
|
||||
* @param {object} aValue
|
||||
* The object to test.
|
||||
* @param {object} aExpected
|
||||
* The object to strictly compare with.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
notDeepEqual: function notEqual(aValue, aExpected, aMessage) {
|
||||
let condition = !this._deepEqual(aValue, aExpected);
|
||||
try {
|
||||
var aValueString = JSON.stringify(aValue);
|
||||
} catch (e) {
|
||||
var aValueString = String(aValue);
|
||||
}
|
||||
try {
|
||||
var aExpectedString = JSON.stringify(aExpected);
|
||||
} catch (e) {
|
||||
var aExpectedString = String(aExpected);
|
||||
}
|
||||
|
||||
let diagnosis = "'" + aValueString + "' should not equal '" +
|
||||
aExpectedString + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if the regular expression matches the string.
|
||||
*
|
||||
* @param {string} aString
|
||||
* String to test.
|
||||
* @param {RegEx} aRegex
|
||||
* Regular expression to use for testing that a match exists.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
match: function Assert_match(aString, aRegex, aMessage) {
|
||||
// XXX Bug 634948
|
||||
// Regex objects are transformed to strings when evaluated in a sandbox
|
||||
// For now lets re-create the regex from its string representation
|
||||
let pattern = "";
|
||||
let flags = "";
|
||||
try {
|
||||
let matches = aRegex.toString().match(/\/(.*)\/(.*)/);
|
||||
|
||||
pattern = matches[1];
|
||||
flags = matches[2];
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
let regex = new RegExp(pattern, flags);
|
||||
let condition = (aString.match(regex) !== null);
|
||||
let diagnosis = "'" + regex + "' matches for '" + aString + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if the regular expression does not match the string.
|
||||
*
|
||||
* @param {string} aString
|
||||
* String to test.
|
||||
* @param {RegEx} aRegex
|
||||
* Regular expression to use for testing that a match does not exist.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
notMatch: function Assert_notMatch(aString, aRegex, aMessage) {
|
||||
// XXX Bug 634948
|
||||
// Regex objects are transformed to strings when evaluated in a sandbox
|
||||
// For now lets re-create the regex from its string representation
|
||||
let pattern = flags = "";
|
||||
try {
|
||||
let matches = aRegex.toString().match(/\/(.*)\/(.*)/);
|
||||
|
||||
pattern = matches[1];
|
||||
flags = matches[2];
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
let regex = new RegExp(pattern, flags);
|
||||
let condition = (aString.match(regex) === null);
|
||||
let diagnosis = "'" + regex + "' doesn't match for '" + aString + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Test if a code block throws an exception.
|
||||
*
|
||||
* @param {string} block
|
||||
* function to call to test for exception
|
||||
* @param {RegEx} error
|
||||
* the expected error class
|
||||
* @param {string} message
|
||||
* message to present if assertion fails
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
throws : function Assert_throws(block, /*optional*/error, /*optional*/message) {
|
||||
return this._throws.apply(this, [true].concat(Array.prototype.slice.call(arguments)));
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if a code block doesn't throw an exception.
|
||||
*
|
||||
* @param {string} block
|
||||
* function to call to test for exception
|
||||
* @param {RegEx} error
|
||||
* the expected error class
|
||||
* @param {string} message
|
||||
* message to present if assertion fails
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
doesNotThrow : function Assert_doesNotThrow(block, /*optional*/error, /*optional*/message) {
|
||||
return this._throws.apply(this, [false].concat(Array.prototype.slice.call(arguments)));
|
||||
},
|
||||
|
||||
/* Tests whether a code block throws the expected exception
|
||||
class. helper for throws() and doesNotThrow()
|
||||
|
||||
adapted from node.js's assert._throws()
|
||||
https://github.com/joyent/node/blob/master/lib/assert.js
|
||||
*/
|
||||
_throws : function Assert__throws(shouldThrow, block, expected, message) {
|
||||
var actual;
|
||||
|
||||
if (typeof expected === 'string') {
|
||||
message = expected;
|
||||
expected = null;
|
||||
}
|
||||
|
||||
try {
|
||||
block();
|
||||
} catch (e) {
|
||||
actual = e;
|
||||
}
|
||||
|
||||
message = (expected && expected.name ? ' (' + expected.name + ').' : '.') +
|
||||
(message ? ' ' + message : '.');
|
||||
|
||||
if (shouldThrow && !actual) {
|
||||
return this._test(false, message, 'Missing expected exception');
|
||||
}
|
||||
|
||||
if (!shouldThrow && this._expectedException(actual, expected)) {
|
||||
return this._test(false, message, 'Got unwanted exception');
|
||||
}
|
||||
|
||||
if ((shouldThrow && actual && expected &&
|
||||
!this._expectedException(actual, expected)) || (!shouldThrow && actual)) {
|
||||
throw actual;
|
||||
}
|
||||
|
||||
return this._test(true, message);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if the string contains the pattern.
|
||||
*
|
||||
* @param {String} aString String to test.
|
||||
* @param {String} aPattern Pattern to look for in the string
|
||||
* @param {String} aMessage Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {Boolean} Result of the test.
|
||||
*/
|
||||
contain: function Assert_contain(aString, aPattern, aMessage) {
|
||||
let condition = (aString.indexOf(aPattern) !== -1);
|
||||
let diagnosis = "'" + aString + "' should contain '" + aPattern + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if the string does not contain the pattern.
|
||||
*
|
||||
* @param {String} aString String to test.
|
||||
* @param {String} aPattern Pattern to look for in the string
|
||||
* @param {String} aMessage Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {Boolean} Result of the test.
|
||||
*/
|
||||
notContain: function Assert_notContain(aString, aPattern, aMessage) {
|
||||
let condition = (aString.indexOf(aPattern) === -1);
|
||||
let diagnosis = "'" + aString + "' should not contain '" + aPattern + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
* Waits for the callback evaluates to true
|
||||
*
|
||||
* @param {Function} aCallback
|
||||
* Callback for evaluation
|
||||
* @param {String} aMessage
|
||||
* Message to show for result
|
||||
* @param {Number} aTimeout
|
||||
* Timeout in waiting for evaluation
|
||||
* @param {Number} aInterval
|
||||
* Interval between evaluation attempts
|
||||
* @param {Object} aThisObject
|
||||
* this object
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {Boolean} Result of the test.
|
||||
*/
|
||||
waitFor: function Assert_waitFor(aCallback, aMessage, aTimeout, aInterval, aThisObject) {
|
||||
var timeout = aTimeout || 5000;
|
||||
var interval = aInterval || 100;
|
||||
|
||||
var self = {
|
||||
timeIsUp: false,
|
||||
result: aCallback.call(aThisObject)
|
||||
};
|
||||
var deadline = Date.now() + timeout;
|
||||
|
||||
function wait() {
|
||||
if (self.result !== true) {
|
||||
self.result = aCallback.call(aThisObject);
|
||||
self.timeIsUp = Date.now() > deadline;
|
||||
}
|
||||
}
|
||||
|
||||
var hwindow = Services.appShell.hiddenDOMWindow;
|
||||
var timeoutInterval = hwindow.setInterval(wait, interval);
|
||||
var thread = Services.tm.currentThread;
|
||||
|
||||
Services.tm.spinEventLoopUntil(() => {
|
||||
let type = typeof(self.result);
|
||||
if (type !== 'boolean') {
|
||||
throw TypeError("waitFor() callback has to return a boolean" +
|
||||
" instead of '" + type + "'");
|
||||
}
|
||||
|
||||
return self.result === true || self.timeIsUp;
|
||||
});
|
||||
|
||||
hwindow.clearInterval(timeoutInterval);
|
||||
|
||||
if (self.result !== true && self.timeIsUp) {
|
||||
aMessage = aMessage || arguments.callee.name + ": Timeout exceeded for '" + aCallback + "'";
|
||||
throw new errors.TimeoutError(aMessage);
|
||||
}
|
||||
|
||||
broker.pass({'function':'assert.waitFor()'});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* non-fatal assertions */
|
||||
var Expect = function () {}
|
||||
|
||||
Expect.prototype = new Assert();
|
||||
|
||||
/**
|
||||
* Log a test as failing by adding a fail frame.
|
||||
*
|
||||
* @param {object} aResult
|
||||
* Test result details used for reporting.
|
||||
* <dl>
|
||||
* <dd>fileName</dd>
|
||||
* <dt>Name of the file in which the assertion failed.</dt>
|
||||
* <dd>functionName</dd>
|
||||
* <dt>Function in which the assertion failed.</dt>
|
||||
* <dd>lineNumber</dd>
|
||||
* <dt>Line number of the file in which the assertion failed.</dt>
|
||||
* <dd>message</dd>
|
||||
* <dt>Message why the assertion failed.</dt>
|
||||
* </dl>
|
||||
*/
|
||||
Expect.prototype._logFail = function Expect__logFail(aResult) {
|
||||
broker.fail({fail: aResult});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the callback evaluates to true
|
||||
*
|
||||
* @param {Function} aCallback
|
||||
* Callback for evaluation
|
||||
* @param {String} aMessage
|
||||
* Message to show for result
|
||||
* @param {Number} aTimeout
|
||||
* Timeout in waiting for evaluation
|
||||
* @param {Number} aInterval
|
||||
* Interval between evaluation attempts
|
||||
* @param {Object} aThisObject
|
||||
* this object
|
||||
*/
|
||||
Expect.prototype.waitFor = function Expect_waitFor(aCallback, aMessage, aTimeout, aInterval, aThisObject) {
|
||||
let condition = true;
|
||||
let message = aMessage;
|
||||
|
||||
try {
|
||||
Assert.prototype.waitFor.apply(this, arguments);
|
||||
}
|
||||
catch (ex) {
|
||||
if (!(ex instanceof errors.AssertionError)) {
|
||||
throw ex;
|
||||
}
|
||||
message = ex.message;
|
||||
condition = false;
|
||||
}
|
||||
|
||||
return this._test(condition, message);
|
||||
}
|
|
@ -1,290 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* @namespace Defines the Mozmill driver for global actions
|
||||
*/
|
||||
var driver = exports;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// Temporarily include utils module to re-use sleep
|
||||
var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
|
||||
var mozmill = {}; Cu.import("resource://mozmill/driver/mozmill.js", mozmill);
|
||||
var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
|
||||
|
||||
/**
|
||||
* Gets the topmost browser window. If there are none at that time, optionally
|
||||
* opens one. Otherwise will raise an exception if none are found.
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {Boolean] [aOpenIfNone=true] Open a new browser window if none are found.
|
||||
* @returns {DOMWindow}
|
||||
*/
|
||||
function getBrowserWindow(aOpenIfNone) {
|
||||
// Set default
|
||||
if (typeof aOpenIfNone === 'undefined') {
|
||||
aOpenIfNone = true;
|
||||
}
|
||||
|
||||
// If implicit open is off, turn on strict checking, and vice versa.
|
||||
let win = getTopmostWindowByType("navigator:browser", !aOpenIfNone);
|
||||
|
||||
// Can just assume automatic open here. If we didn't want it and nothing found,
|
||||
// we already raised above when getTopmostWindow was called.
|
||||
if (!win)
|
||||
win = openBrowserWindow();
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the hidden window on OS X
|
||||
*
|
||||
* @memberOf driver
|
||||
* @returns {DOMWindow} The hidden window
|
||||
*/
|
||||
function getHiddenWindow() {
|
||||
return Services.appShell.hiddenDOMWindow;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Opens a new browser window
|
||||
*
|
||||
* @memberOf driver
|
||||
* @returns {DOMWindow}
|
||||
*/
|
||||
function openBrowserWindow() {
|
||||
// On OS X we have to be able to create a new browser window even with no other
|
||||
// window open. Therefore we have to use the hidden window. On other platforms
|
||||
// at least one remaining browser window has to exist.
|
||||
var win = mozmill.isMac ? getHiddenWindow() :
|
||||
getTopmostWindowByType("navigator:browser", true);
|
||||
return win.OpenBrowserWindow();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pause the test execution for the given amount of time
|
||||
*
|
||||
* @type utils.sleep
|
||||
* @memberOf driver
|
||||
*/
|
||||
var sleep = utils.sleep;
|
||||
|
||||
/**
|
||||
* Wait until the given condition via the callback returns true.
|
||||
*
|
||||
* @type utils.waitFor
|
||||
* @memberOf driver
|
||||
*/
|
||||
var waitFor = assertions.Assert.waitFor;
|
||||
|
||||
//
|
||||
// INTERNAL WINDOW ENUMERATIONS
|
||||
//
|
||||
|
||||
/**
|
||||
* Internal function to build a list of DOM windows using a given enumerator
|
||||
* and filter.
|
||||
*
|
||||
* @private
|
||||
* @memberOf driver
|
||||
* @param {nsISimpleEnumerator} aEnumerator Window enumerator to use.
|
||||
* @param {Function} [aFilterCallback] Function which is used to filter windows.
|
||||
* @param {Boolean} [aStrict=true] Throw an error if no windows found
|
||||
*
|
||||
* @returns {DOMWindow[]} The windows found, in the same order as the enumerator.
|
||||
*/
|
||||
function _getWindows(aEnumerator, aFilterCallback, aStrict) {
|
||||
// Set default
|
||||
if (typeof aStrict === 'undefined')
|
||||
aStrict = true;
|
||||
|
||||
let windows = [];
|
||||
|
||||
while (aEnumerator.hasMoreElements()) {
|
||||
let window = aEnumerator.getNext();
|
||||
|
||||
if (!aFilterCallback || aFilterCallback(window)) {
|
||||
windows.push(window);
|
||||
}
|
||||
}
|
||||
|
||||
// If this list is empty and we're strict, throw an error
|
||||
if (windows.length === 0 && aStrict) {
|
||||
var message = 'No windows were found';
|
||||
|
||||
// We'll throw a more detailed error if a filter was used.
|
||||
if (aFilterCallback && aFilterCallback.name)
|
||||
message += ' using filter "' + aFilterCallback.name + '"';
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return windows;
|
||||
}
|
||||
|
||||
//
|
||||
// FILTER CALLBACKS
|
||||
//
|
||||
|
||||
/**
|
||||
* Generator of a closure to filter a window based by a method
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {String} aName Name of the method in the window object.
|
||||
* @returns {Boolean} True if the condition is met.
|
||||
*/
|
||||
function windowFilterByMethod(aName) {
|
||||
return function byMethod(aWindow) { return (aName in aWindow); }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generator of a closure to filter a window based by the its title
|
||||
*
|
||||
* @param {String} aTitle Title of the window.
|
||||
* @returns {Boolean} True if the condition is met.
|
||||
*/
|
||||
function windowFilterByTitle(aTitle) {
|
||||
return function byTitle(aWindow) { return (aWindow.document.title === aTitle); }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generator of a closure to filter a window based by the its type
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {String} aType Type of the window.
|
||||
* @returns {Boolean} True if the condition is met.
|
||||
*/
|
||||
function windowFilterByType(aType) {
|
||||
return function byType(aWindow) {
|
||||
var type = aWindow.document.documentElement.getAttribute("windowtype");
|
||||
return (type === aType);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// WINDOW LIST RETRIEVAL FUNCTIONS
|
||||
//
|
||||
|
||||
/**
|
||||
* Retrieves a sorted list of open windows based on their age (newest to oldest),
|
||||
* optionally matching filter criteria.
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {Function} [aFilterCallback] Function which is used to filter windows.
|
||||
* @param {Boolean} [aStrict=true] Throw an error if no windows found
|
||||
*
|
||||
* @returns {DOMWindow[]} List of windows.
|
||||
*/
|
||||
function getWindowsByAge(aFilterCallback, aStrict) {
|
||||
var windows = _getWindows(Services.wm.getEnumerator(""),
|
||||
aFilterCallback, aStrict);
|
||||
|
||||
// Reverse the list, since naturally comes back old->new
|
||||
return windows.reverse();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a sorted list of open windows based on their z order (topmost first),
|
||||
* optionally matching filter criteria.
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {Function} [aFilterCallback] Function which is used to filter windows.
|
||||
* @param {Boolean} [aStrict=true] Throw an error if no windows found
|
||||
*
|
||||
* @returns {DOMWindow[]} List of windows.
|
||||
*/
|
||||
function getWindowsByZOrder(aFilterCallback, aStrict) {
|
||||
return _getWindows(Services.wm.getZOrderDOMWindowEnumerator("", true),
|
||||
aFilterCallback, aStrict);
|
||||
}
|
||||
|
||||
//
|
||||
// SINGLE WINDOW RETRIEVAL FUNCTIONS
|
||||
//
|
||||
|
||||
/**
|
||||
* Retrieves the last opened window, optionally matching filter criteria.
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {Function} [aFilterCallback] Function which is used to filter windows.
|
||||
* @param {Boolean} [aStrict=true] If true, throws error if no window found.
|
||||
*
|
||||
* @returns {DOMWindow} The window, or null if none found and aStrict == false
|
||||
*/
|
||||
function getNewestWindow(aFilterCallback, aStrict) {
|
||||
var windows = getWindowsByAge(aFilterCallback, aStrict);
|
||||
return windows.length ? windows[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the topmost window, optionally matching filter criteria.
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {Function} [aFilterCallback] Function which is used to filter windows.
|
||||
* @param {Boolean} [aStrict=true] If true, throws error if no window found.
|
||||
*
|
||||
* @returns {DOMWindow} The window, or null if none found and aStrict == false
|
||||
*/
|
||||
function getTopmostWindow(aFilterCallback, aStrict) {
|
||||
var windows = getWindowsByZOrder(aFilterCallback, aStrict);
|
||||
return windows.length ? windows[0] : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the topmost window given by the window type
|
||||
*
|
||||
* XXX: Bug 462222
|
||||
* This function has to be used instead of getTopmostWindow until the
|
||||
* underlying platform bug has been fixed.
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {String} [aWindowType=null] Window type to query for
|
||||
* @param {Boolean} [aStrict=true] Throw an error if no windows found
|
||||
*
|
||||
* @returns {DOMWindow} The window, or null if none found and aStrict == false
|
||||
*/
|
||||
function getTopmostWindowByType(aWindowType, aStrict) {
|
||||
if (typeof aStrict === 'undefined')
|
||||
aStrict = true;
|
||||
|
||||
var win = Services.wm.getMostRecentWindow(aWindowType);
|
||||
|
||||
if (win === null && aStrict) {
|
||||
var message = 'No windows of type "' + aWindowType + '" were found';
|
||||
throw new errors.UnexpectedError(message);
|
||||
}
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
|
||||
// Export of functions
|
||||
driver.getBrowserWindow = getBrowserWindow;
|
||||
driver.getHiddenWindow = getHiddenWindow;
|
||||
driver.openBrowserWindow = openBrowserWindow;
|
||||
driver.sleep = sleep;
|
||||
driver.waitFor = waitFor;
|
||||
|
||||
driver.windowFilterByMethod = windowFilterByMethod;
|
||||
driver.windowFilterByTitle = windowFilterByTitle;
|
||||
driver.windowFilterByType = windowFilterByType;
|
||||
|
||||
driver.getWindowsByAge = getWindowsByAge;
|
||||
driver.getNewestWindow = getNewestWindow;
|
||||
driver.getTopmostWindowByType = getTopmostWindowByType;
|
||||
|
||||
|
||||
// XXX Bug: 462222
|
||||
// Currently those functions cannot be used. So they shouldn't be exported.
|
||||
//driver.getWindowsByZOrder = getWindowsByZOrder;
|
||||
//driver.getTopmostWindow = getTopmostWindow;
|
|
@ -1,102 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['BaseError',
|
||||
'ApplicationQuitError',
|
||||
'AssertionError',
|
||||
'TimeoutError'];
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance of a base error
|
||||
*
|
||||
* @class Represents the base for custom errors
|
||||
* @param {string} [aMessage=Error().message]
|
||||
* The error message to show
|
||||
* @param {string} [aFileName=Error().fileName]
|
||||
* The file name where the error has been raised
|
||||
* @param {string} [aLineNumber=Error().lineNumber]
|
||||
* The line number of the file where the error has been raised
|
||||
* @param {string} [aFunctionName=undefined]
|
||||
* The function name in which the error has been raised
|
||||
*/
|
||||
function BaseError(aMessage, aFileName, aLineNumber, aFunctionName) {
|
||||
this.name = this.constructor.name;
|
||||
|
||||
var err = new Error();
|
||||
if (err.stack) {
|
||||
this.stack = err.stack;
|
||||
}
|
||||
|
||||
this.message = aMessage || err.message;
|
||||
this.fileName = aFileName || err.fileName;
|
||||
this.lineNumber = aLineNumber || err.lineNumber;
|
||||
this.functionName = aFunctionName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance of an application quit error used by Mozmill to
|
||||
* indicate that the application is going to shutdown
|
||||
*
|
||||
* @class Represents an error object thrown when the application is going to shutdown
|
||||
* @param {string} [aMessage=Error().message]
|
||||
* The error message to show
|
||||
* @param {string} [aFileName=Error().fileName]
|
||||
* The file name where the error has been raised
|
||||
* @param {string} [aLineNumber=Error().lineNumber]
|
||||
* The line number of the file where the error has been raised
|
||||
* @param {string} [aFunctionName=undefined]
|
||||
* The function name in which the error has been raised
|
||||
*/
|
||||
function ApplicationQuitError(aMessage, aFileName, aLineNumber, aFunctionName) {
|
||||
BaseError.apply(this, arguments);
|
||||
}
|
||||
|
||||
ApplicationQuitError.prototype = Object.create(BaseError.prototype, {
|
||||
constructor : { value : ApplicationQuitError }
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance of an assertion error
|
||||
*
|
||||
* @class Represents an error object thrown by failing assertions
|
||||
* @param {string} [aMessage=Error().message]
|
||||
* The error message to show
|
||||
* @param {string} [aFileName=Error().fileName]
|
||||
* The file name where the error has been raised
|
||||
* @param {string} [aLineNumber=Error().lineNumber]
|
||||
* The line number of the file where the error has been raised
|
||||
* @param {string} [aFunctionName=undefined]
|
||||
* The function name in which the error has been raised
|
||||
*/
|
||||
function AssertionError(aMessage, aFileName, aLineNumber, aFunctionName) {
|
||||
BaseError.apply(this, arguments);
|
||||
}
|
||||
|
||||
AssertionError.prototype = Object.create(BaseError.prototype, {
|
||||
constructor : { value : AssertionError }
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a new instance of a timeout error
|
||||
*
|
||||
* @class Represents an error object thrown by failing assertions
|
||||
* @param {string} [aMessage=Error().message]
|
||||
* The error message to show
|
||||
* @param {string} [aFileName=Error().fileName]
|
||||
* The file name where the error has been raised
|
||||
* @param {string} [aLineNumber=Error().lineNumber]
|
||||
* The line number of the file where the error has been raised
|
||||
* @param {string} [aFunctionName=undefined]
|
||||
* The function name in which the error has been raised
|
||||
*/
|
||||
function TimeoutError(aMessage, aFileName, aLineNumber, aFunctionName) {
|
||||
AssertionError.apply(this, arguments);
|
||||
}
|
||||
|
||||
TimeoutError.prototype = Object.create(AssertionError.prototype, {
|
||||
constructor : { value : TimeoutError }
|
||||
});
|
|
@ -1,787 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['Collector','Runner','events', 'runTestFile', 'log',
|
||||
'timers', 'persisted', 'shutdownApplication'];
|
||||
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
var Cu = Components.utils;
|
||||
|
||||
const TIMEOUT_SHUTDOWN_HTTPD = 15000;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
Cu.import('resource://mozmill/stdlib/httpd.js');
|
||||
|
||||
var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
|
||||
var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
|
||||
var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors);
|
||||
var os = {}; Cu.import('resource://mozmill/stdlib/os.js', os);
|
||||
var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings);
|
||||
var arrays = {}; Cu.import('resource://mozmill/stdlib/arrays.js', arrays);
|
||||
var withs = {}; Cu.import('resource://mozmill/stdlib/withs.js', withs);
|
||||
var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
|
||||
|
||||
var securableModule = {};
|
||||
Cu.import('resource://mozmill/stdlib/securable-module.js', securableModule);
|
||||
|
||||
var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
|
||||
var httpd = null;
|
||||
var persisted = {};
|
||||
|
||||
var assert = new assertions.Assert();
|
||||
var expect = new assertions.Expect();
|
||||
|
||||
var mozmill = undefined;
|
||||
var mozelement = undefined;
|
||||
var modules = undefined;
|
||||
|
||||
var timers = [];
|
||||
|
||||
|
||||
/**
|
||||
* Shutdown or restart the application
|
||||
*
|
||||
* @param {boolean} [aFlags=undefined]
|
||||
* Additional flags how to handle the shutdown or restart.
|
||||
* @see https://developer.mozilla.org/nsIAppStartup#Attributes
|
||||
*/
|
||||
function shutdownApplication(aFlags) {
|
||||
var flags = Ci.nsIAppStartup.eForceQuit;
|
||||
|
||||
if (aFlags) {
|
||||
flags |= aFlags;
|
||||
}
|
||||
|
||||
// Send a request to shutdown the application. That will allow us and other
|
||||
// components to finish up with any shutdown code. Please note that we don't
|
||||
// care if other components or add-ons want to prevent this via cancelQuit,
|
||||
// we really force the shutdown.
|
||||
let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
|
||||
createInstance(Components.interfaces.nsISupportsPRBool);
|
||||
Services.obs.notifyObservers(cancelQuit, "quit-application-requested");
|
||||
|
||||
// Use a timer to trigger the application restart, which will allow us to
|
||||
// send an ACK packet via jsbridge if the method has been called via Python.
|
||||
var event = {
|
||||
notify: function(timer) {
|
||||
Services.startup.quit(flags);
|
||||
}
|
||||
}
|
||||
|
||||
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
timer.initWithCallback(event, 100, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
|
||||
function stateChangeBase(possibilties, restrictions, target, cmeta, v) {
|
||||
if (possibilties) {
|
||||
if (!arrays.inArray(possibilties, v)) {
|
||||
// TODO Error value not in this.poss
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (restrictions) {
|
||||
for (var i in restrictions) {
|
||||
var r = restrictions[i];
|
||||
if (!r(v)) {
|
||||
// TODO error value did not pass restriction
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fire jsbridge notification, logging notification, listener notifications
|
||||
events[target] = v;
|
||||
events.fireEvent(cmeta, target);
|
||||
}
|
||||
|
||||
|
||||
var events = {
|
||||
appQuit : false,
|
||||
currentModule : null,
|
||||
currentState : null,
|
||||
currentTest : null,
|
||||
shutdownRequested : false,
|
||||
userShutdown : null,
|
||||
userShutdownTimer : null,
|
||||
|
||||
listeners : {},
|
||||
globalListeners : []
|
||||
}
|
||||
|
||||
events.setState = function (v) {
|
||||
return stateChangeBase(['dependencies', 'setupModule', 'teardownModule',
|
||||
'test', 'setupTest', 'teardownTest', 'collection'],
|
||||
null, 'currentState', 'setState', v);
|
||||
}
|
||||
|
||||
events.toggleUserShutdown = function (obj){
|
||||
if (!this.userShutdown) {
|
||||
this.userShutdown = obj;
|
||||
|
||||
var event = {
|
||||
notify: function(timer) {
|
||||
events.toggleUserShutdown(obj);
|
||||
}
|
||||
}
|
||||
|
||||
this.userShutdownTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this.userShutdownTimer.initWithCallback(event, obj.timeout, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
|
||||
} else {
|
||||
this.userShutdownTimer.cancel();
|
||||
|
||||
// If the application is not going to shutdown, the user shutdown failed and
|
||||
// we have to force a shutdown.
|
||||
if (!events.appQuit) {
|
||||
this.fail({'function':'events.toggleUserShutdown',
|
||||
'message':'Shutdown expected but none detected before timeout',
|
||||
'userShutdown': obj});
|
||||
|
||||
var flags = Ci.nsIAppStartup.eAttemptQuit;
|
||||
if (events.isRestartShutdown()) {
|
||||
flags |= Ci.nsIAppStartup.eRestart;
|
||||
}
|
||||
|
||||
shutdownApplication(flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
events.isUserShutdown = function () {
|
||||
return this.userShutdown ? this.userShutdown["user"] : false;
|
||||
}
|
||||
|
||||
events.isRestartShutdown = function () {
|
||||
return this.userShutdown.restart;
|
||||
}
|
||||
|
||||
events.startShutdown = function (obj) {
|
||||
events.fireEvent('shutdown', obj);
|
||||
|
||||
if (obj["user"]) {
|
||||
events.toggleUserShutdown(obj);
|
||||
} else {
|
||||
shutdownApplication(obj.flags);
|
||||
}
|
||||
}
|
||||
|
||||
events.setTest = function (test) {
|
||||
test.__start__ = Date.now();
|
||||
test.__passes__ = [];
|
||||
test.__fails__ = [];
|
||||
|
||||
events.currentTest = test;
|
||||
|
||||
var obj = {'filename': events.currentModule.__file__,
|
||||
'name': test.__name__}
|
||||
events.fireEvent('setTest', obj);
|
||||
}
|
||||
|
||||
events.endTest = function (test) {
|
||||
// use the current test unless specified
|
||||
if (test === undefined) {
|
||||
test = events.currentTest;
|
||||
}
|
||||
|
||||
// If no test is set it has already been reported. Beside that we don't want
|
||||
// to report it a second time.
|
||||
if (!test || test.status === 'done')
|
||||
return;
|
||||
|
||||
// report the end of a test
|
||||
test.__end__ = Date.now();
|
||||
test.status = 'done';
|
||||
|
||||
var obj = {'filename': events.currentModule.__file__,
|
||||
'passed': test.__passes__.length,
|
||||
'failed': test.__fails__.length,
|
||||
'passes': test.__passes__,
|
||||
'fails' : test.__fails__,
|
||||
'name' : test.__name__,
|
||||
'time_start': test.__start__,
|
||||
'time_end': test.__end__}
|
||||
|
||||
if (test.skipped) {
|
||||
obj['skipped'] = true;
|
||||
obj.skipped_reason = test.skipped_reason;
|
||||
}
|
||||
|
||||
if (test.meta) {
|
||||
obj.meta = test.meta;
|
||||
}
|
||||
|
||||
// Report the test result only if the test is a true test or if it is failing
|
||||
if (withs.startsWith(test.__name__, "test") || test.__fails__.length > 0) {
|
||||
events.fireEvent('endTest', obj);
|
||||
}
|
||||
}
|
||||
|
||||
events.setModule = function (aModule) {
|
||||
aModule.__start__ = Date.now();
|
||||
aModule.__status__ = 'running';
|
||||
|
||||
var result = stateChangeBase(null,
|
||||
[function (aModule) {return (aModule.__file__ != undefined)}],
|
||||
'currentModule', 'setModule', aModule);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
events.endModule = function (aModule) {
|
||||
// It should only reported once, so check if it already has been done
|
||||
if (aModule.__status__ === 'done')
|
||||
return;
|
||||
|
||||
aModule.__end__ = Date.now();
|
||||
aModule.__status__ = 'done';
|
||||
|
||||
var obj = {
|
||||
'filename': aModule.__file__,
|
||||
'time_start': aModule.__start__,
|
||||
'time_end': aModule.__end__
|
||||
}
|
||||
|
||||
events.fireEvent('endModule', obj);
|
||||
}
|
||||
|
||||
events.pass = function (obj) {
|
||||
// a low level event, such as a keystroke, succeeds
|
||||
if (events.currentTest) {
|
||||
events.currentTest.__passes__.push(obj);
|
||||
}
|
||||
|
||||
for (var timer of timers) {
|
||||
timer.actions.push(
|
||||
{"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__,
|
||||
"obj": obj,
|
||||
"result": "pass"}
|
||||
);
|
||||
}
|
||||
|
||||
events.fireEvent('pass', obj);
|
||||
}
|
||||
|
||||
events.fail = function (obj) {
|
||||
var error = obj.exception;
|
||||
|
||||
if (error) {
|
||||
// Error objects aren't enumerable https://bugzilla.mozilla.org/show_bug.cgi?id=637207
|
||||
obj.exception = {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
lineNumber: error.lineNumber,
|
||||
fileName: error.fileName,
|
||||
stack: error.stack
|
||||
};
|
||||
}
|
||||
|
||||
// a low level event, such as a keystroke, fails
|
||||
if (events.currentTest) {
|
||||
events.currentTest.__fails__.push(obj);
|
||||
}
|
||||
|
||||
for (var timer of timers) {
|
||||
timer.actions.push(
|
||||
{"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__,
|
||||
"obj": obj,
|
||||
"result": "fail"}
|
||||
);
|
||||
}
|
||||
|
||||
events.fireEvent('fail', obj);
|
||||
}
|
||||
|
||||
events.skip = function (reason) {
|
||||
// this is used to report skips associated with setupModule and nothing else
|
||||
events.currentTest.skipped = true;
|
||||
events.currentTest.skipped_reason = reason;
|
||||
|
||||
for (var timer of timers) {
|
||||
timer.actions.push(
|
||||
{"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__,
|
||||
"obj": reason,
|
||||
"result": "skip"}
|
||||
);
|
||||
}
|
||||
|
||||
events.fireEvent('skip', reason);
|
||||
}
|
||||
|
||||
events.fireEvent = function (name, obj) {
|
||||
if (events.appQuit) {
|
||||
// dump('* Event discarded: ' + name + ' ' + JSON.stringify(obj) + '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.listeners[name]) {
|
||||
for (var i in this.listeners[name]) {
|
||||
this.listeners[name][i](obj);
|
||||
}
|
||||
}
|
||||
|
||||
for (var listener of this.globalListeners) {
|
||||
listener(name, obj);
|
||||
}
|
||||
}
|
||||
|
||||
events.addListener = function (name, listener) {
|
||||
if (this.listeners[name]) {
|
||||
this.listeners[name].push(listener);
|
||||
} else if (name == '') {
|
||||
this.globalListeners.push(listener)
|
||||
} else {
|
||||
this.listeners[name] = [listener];
|
||||
}
|
||||
}
|
||||
|
||||
events.removeListener = function (listener) {
|
||||
for (var listenerIndex in this.listeners) {
|
||||
var e = this.listeners[listenerIndex];
|
||||
|
||||
for (var i in e){
|
||||
if (e[i] == listener) {
|
||||
this.listeners[listenerIndex] = arrays.remove(e, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i in this.globalListeners) {
|
||||
if (this.globalListeners[i] == listener) {
|
||||
this.globalListeners = arrays.remove(this.globalListeners, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
events.persist = function () {
|
||||
try {
|
||||
events.fireEvent('persist', persisted);
|
||||
} catch (e) {
|
||||
events.fireEvent('error', "persist serialization failed.")
|
||||
}
|
||||
}
|
||||
|
||||
events.firePythonCallback = function (obj) {
|
||||
obj['test'] = events.currentModule.__file__;
|
||||
events.fireEvent('firePythonCallback', obj);
|
||||
}
|
||||
|
||||
events.screenshot = function (obj) {
|
||||
// Find the name of the test function
|
||||
for (var attr in events.currentModule) {
|
||||
if (events.currentModule[attr] == events.currentTest) {
|
||||
var testName = attr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
obj['test_file'] = events.currentModule.__file__;
|
||||
obj['test_name'] = testName;
|
||||
events.fireEvent('screenshot', obj);
|
||||
}
|
||||
|
||||
var log = function (obj) {
|
||||
events.fireEvent('log', obj);
|
||||
}
|
||||
|
||||
// Register the listeners
|
||||
broker.addObject({'endTest': events.endTest,
|
||||
'fail': events.fail,
|
||||
'firePythonCallback': events.firePythonCallback,
|
||||
'log': log,
|
||||
'pass': events.pass,
|
||||
'persist': events.persist,
|
||||
'screenshot': events.screenshot,
|
||||
'shutdown': events.startShutdown,
|
||||
});
|
||||
|
||||
try {
|
||||
Cu.import('resource://jsbridge/modules/Events.jsm');
|
||||
|
||||
events.addListener('', function (name, obj) {
|
||||
Events.fireEvent('mozmill.' + name, obj);
|
||||
});
|
||||
} catch (e) {
|
||||
Services.console.logStringMessage("Event module of JSBridge not available.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Observer for notifications when the application is going to shutdown
|
||||
*/
|
||||
function AppQuitObserver() {
|
||||
this.runner = null;
|
||||
|
||||
Services.obs.addObserver(this, "quit-application-requested");
|
||||
}
|
||||
|
||||
AppQuitObserver.prototype = {
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "quit-application-requested":
|
||||
Services.obs.removeObserver(this, "quit-application-requested");
|
||||
|
||||
// If we observe a quit notification make sure to send the
|
||||
// results of the current test. In those cases we don't reach
|
||||
// the equivalent code in runTestModule()
|
||||
events.pass({'message': 'AppQuitObserver: ' + JSON.stringify(aData),
|
||||
'userShutdown': events.userShutdown});
|
||||
|
||||
if (this.runner) {
|
||||
this.runner.end();
|
||||
}
|
||||
|
||||
if (httpd) {
|
||||
httpd.stop();
|
||||
}
|
||||
|
||||
events.appQuit = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var appQuitObserver = new AppQuitObserver();
|
||||
|
||||
/**
|
||||
* The collector handles HTTPd.js and initilizing the module
|
||||
*/
|
||||
function Collector() {
|
||||
this.test_modules_by_filename = {};
|
||||
this.testing = [];
|
||||
}
|
||||
|
||||
Collector.prototype.addHttpResource = function (aDirectory, aPath) {
|
||||
var fp = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
fp.initWithPath(os.abspath(aDirectory, this.current_file));
|
||||
|
||||
return httpd.addHttpResource(fp, aPath);
|
||||
}
|
||||
|
||||
Collector.prototype.initTestModule = function (filename, testname) {
|
||||
var test_module = this.loadFile(filename, this);
|
||||
var has_restarted = !(testname == null);
|
||||
test_module.__tests__ = [];
|
||||
|
||||
for (var i in test_module) {
|
||||
if (typeof(test_module[i]) == "function") {
|
||||
test_module[i].__name__ = i;
|
||||
|
||||
// Only run setupModule if we are a single test OR if we are the first
|
||||
// test of a restart chain (don't run it prior to members in a restart
|
||||
// chain)
|
||||
if (i == "setupModule" && !has_restarted) {
|
||||
test_module.__setupModule__ = test_module[i];
|
||||
} else if (i == "setupTest") {
|
||||
test_module.__setupTest__ = test_module[i];
|
||||
} else if (i == "teardownTest") {
|
||||
test_module.__teardownTest__ = test_module[i];
|
||||
} else if (i == "teardownModule") {
|
||||
test_module.__teardownModule__ = test_module[i];
|
||||
} else if (withs.startsWith(i, "test")) {
|
||||
if (testname && (i != testname)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
testname = null;
|
||||
test_module.__tests__.push(test_module[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test_module.collector = this;
|
||||
test_module.status = 'loaded';
|
||||
|
||||
this.test_modules_by_filename[filename] = test_module;
|
||||
|
||||
return test_module;
|
||||
}
|
||||
|
||||
Collector.prototype.loadFile = function (path, collector) {
|
||||
var moduleLoader = new securableModule.Loader({
|
||||
rootPaths: ["resource://mozmill/modules/"],
|
||||
defaultPrincipal: "system",
|
||||
globals : { Cc: Cc,
|
||||
Ci: Ci,
|
||||
Cu: Cu,
|
||||
Cr: Components.results}
|
||||
});
|
||||
|
||||
// load a test module from a file and add some candy
|
||||
var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
file.initWithPath(path);
|
||||
var uri = Services.io.newFileURI(file).spec;
|
||||
|
||||
this.loadTestResources();
|
||||
|
||||
var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
|
||||
var module = new Components.utils.Sandbox(systemPrincipal);
|
||||
module.assert = assert;
|
||||
module.Cc = Cc;
|
||||
module.Ci = Ci;
|
||||
module.Cr = Components.results;
|
||||
module.Cu = Cu;
|
||||
module.collector = collector;
|
||||
module.driver = moduleLoader.require("driver");
|
||||
module.elementslib = mozelement;
|
||||
module.errors = errors;
|
||||
module.expect = expect;
|
||||
module.findElement = mozelement;
|
||||
module.log = log;
|
||||
module.mozmill = mozmill;
|
||||
module.persisted = persisted;
|
||||
|
||||
module.require = function (mod) {
|
||||
var loader = new securableModule.Loader({
|
||||
rootPaths: [Services.io.newFileURI(file.parent).spec,
|
||||
"resource://mozmill/modules/"],
|
||||
defaultPrincipal: "system",
|
||||
globals : { assert: assert,
|
||||
expect: expect,
|
||||
mozmill: mozmill,
|
||||
elementslib: mozelement, // This a quick hack to maintain backwards compatibility with 1.5.x
|
||||
findElement: mozelement,
|
||||
persisted: persisted,
|
||||
Cc: Cc,
|
||||
Ci: Ci,
|
||||
Cu: Cu,
|
||||
log: log }
|
||||
});
|
||||
|
||||
if (modules != undefined) {
|
||||
loader.modules = modules;
|
||||
}
|
||||
|
||||
var retval = loader.require(mod);
|
||||
modules = loader.modules;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (collector != undefined) {
|
||||
collector.current_file = file;
|
||||
collector.current_path = path;
|
||||
}
|
||||
|
||||
try {
|
||||
Services.scriptloader.loadSubScript(uri, module, "UTF-8");
|
||||
} catch (e) {
|
||||
var obj = {
|
||||
'filename': path,
|
||||
'passed': 0,
|
||||
'failed': 1,
|
||||
'passes': [],
|
||||
'fails' : [{'exception' : {
|
||||
message: e.message,
|
||||
filename: e.filename,
|
||||
lineNumber: e.lineNumber}}],
|
||||
'name' :'<TOP_LEVEL>'
|
||||
};
|
||||
|
||||
events.fail({'exception': e});
|
||||
events.fireEvent('endTest', obj);
|
||||
}
|
||||
|
||||
module.__file__ = path;
|
||||
module.__uri__ = uri;
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
Collector.prototype.loadTestResources = function () {
|
||||
// load resources we want in our tests
|
||||
if (mozmill === undefined) {
|
||||
mozmill = {};
|
||||
Cu.import("resource://mozmill/driver/mozmill.js", mozmill);
|
||||
}
|
||||
if (mozelement === undefined) {
|
||||
mozelement = {};
|
||||
Cu.import("resource://mozmill/driver/mozelement.js", mozelement);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function Httpd(aPort) {
|
||||
this.http_port = aPort;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
var srv = new HttpServer();
|
||||
srv.registerContentType("sjs", "sjs");
|
||||
srv.identity.setPrimary("http", "localhost", this.http_port);
|
||||
srv.start(this.http_port);
|
||||
|
||||
this._httpd = srv;
|
||||
break;
|
||||
}
|
||||
catch (e) {
|
||||
// Failure most likely due to port conflict
|
||||
this.http_port++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Httpd.prototype.addHttpResource = function (aDir, aPath) {
|
||||
var path = aPath ? ("/" + aPath + "/") : "/";
|
||||
|
||||
try {
|
||||
this._httpd.registerDirectory(path, aDir);
|
||||
return 'http://localhost:' + this.http_port + path;
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Failure to register directory: " + aDir.path);
|
||||
}
|
||||
};
|
||||
|
||||
Httpd.prototype.stop = function () {
|
||||
if (!this._httpd) {
|
||||
return;
|
||||
}
|
||||
|
||||
var shutdown = false;
|
||||
this._httpd.stop(function () { shutdown = true; });
|
||||
|
||||
assert.waitFor(function () {
|
||||
return shutdown;
|
||||
}, "Local HTTP server has been stopped", TIMEOUT_SHUTDOWN_HTTPD);
|
||||
|
||||
this._httpd = null;
|
||||
};
|
||||
|
||||
function startHTTPd() {
|
||||
if (!httpd) {
|
||||
// Ensure that we start the HTTP server only once during a session
|
||||
httpd = new Httpd(43336);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Runner() {
|
||||
this.collector = new Collector();
|
||||
this.ended = false;
|
||||
|
||||
var m = {}; Cu.import('resource://mozmill/driver/mozmill.js', m);
|
||||
this.platform = m.platform;
|
||||
|
||||
events.fireEvent('startRunner', true);
|
||||
}
|
||||
|
||||
Runner.prototype.end = function () {
|
||||
if (!this.ended) {
|
||||
this.ended = true;
|
||||
|
||||
appQuitObserver.runner = null;
|
||||
|
||||
events.endTest();
|
||||
events.endModule(events.currentModule);
|
||||
events.fireEvent('endRunner', true);
|
||||
events.persist();
|
||||
}
|
||||
};
|
||||
|
||||
Runner.prototype.runTestFile = function (filename, name) {
|
||||
var module = this.collector.initTestModule(filename, name);
|
||||
this.runTestModule(module);
|
||||
};
|
||||
|
||||
Runner.prototype.runTestModule = function (module) {
|
||||
appQuitObserver.runner = this;
|
||||
events.setModule(module);
|
||||
|
||||
// If setupModule passes, run all the tests. Otherwise mark them as skipped.
|
||||
if (this.execFunction(module.__setupModule__, module)) {
|
||||
for (var test of module.__tests__) {
|
||||
if (events.shutdownRequested) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If setupTest passes, run the test. Otherwise mark it as skipped.
|
||||
if (this.execFunction(module.__setupTest__, module)) {
|
||||
this.execFunction(test);
|
||||
} else {
|
||||
this.skipFunction(test, module.__setupTest__.__name__ + " failed");
|
||||
}
|
||||
|
||||
this.execFunction(module.__teardownTest__, module);
|
||||
}
|
||||
|
||||
} else {
|
||||
for (var test of module.__tests__) {
|
||||
this.skipFunction(test, module.__setupModule__.__name__ + " failed");
|
||||
}
|
||||
}
|
||||
|
||||
this.execFunction(module.__teardownModule__, module);
|
||||
events.endModule(module);
|
||||
};
|
||||
|
||||
Runner.prototype.execFunction = function (func, arg) {
|
||||
if (typeof func !== "function" || events.shutdownRequested) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var isTest = withs.startsWith(func.__name__, "test");
|
||||
|
||||
events.setState(isTest ? "test" : func.__name);
|
||||
events.setTest(func);
|
||||
|
||||
// skip excluded platforms
|
||||
if (func.EXCLUDED_PLATFORMS != undefined) {
|
||||
if (arrays.inArray(func.EXCLUDED_PLATFORMS, this.platform)) {
|
||||
events.skip("Platform exclusion");
|
||||
events.endTest(func);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// skip function if requested
|
||||
if (func.__force_skip__ != undefined) {
|
||||
events.skip(func.__force_skip__);
|
||||
events.endTest(func);
|
||||
return false;
|
||||
}
|
||||
|
||||
// execute the test function
|
||||
try {
|
||||
func(arg);
|
||||
} catch (e) {
|
||||
if (e instanceof errors.ApplicationQuitError) {
|
||||
events.shutdownRequested = true;
|
||||
} else {
|
||||
events.fail({'exception': e, 'test': func})
|
||||
}
|
||||
}
|
||||
|
||||
// If a user shutdown has been requested and the function already returned,
|
||||
// we can assume that a shutdown will not happen anymore. We should force a
|
||||
// shutdown then, to prevent the next test from being executed.
|
||||
if (events.isUserShutdown()) {
|
||||
events.shutdownRequested = true;
|
||||
events.toggleUserShutdown(events.userShutdown);
|
||||
}
|
||||
|
||||
events.endTest(func);
|
||||
return events.currentTest.__fails__.length == 0;
|
||||
};
|
||||
|
||||
function runTestFile(filename, name) {
|
||||
var runner = new Runner();
|
||||
runner.runTestFile(filename, name);
|
||||
runner.end();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Runner.prototype.skipFunction = function (func, message) {
|
||||
events.setTest(func);
|
||||
events.skip(message);
|
||||
events.endTest(func);
|
||||
};
|
|
@ -1,71 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* @namespace Defines useful methods to work with localized content
|
||||
*/
|
||||
var l10n = exports;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
/**
|
||||
* Retrieve the localized content for a given DTD entity
|
||||
*
|
||||
* @memberOf l10n
|
||||
* @param {String[]} aDTDs Array of URLs for DTD files.
|
||||
* @param {String} aEntityId ID of the entity to get the localized content of.
|
||||
*
|
||||
* @returns {String} Localized content
|
||||
*/
|
||||
function getEntity(aDTDs, aEntityId) {
|
||||
// Add xhtml11.dtd to prevent missing entity errors with XHTML files
|
||||
aDTDs.push("resource:///res/dtd/xhtml11.dtd");
|
||||
|
||||
// Build a string of external entities
|
||||
var references = "";
|
||||
for (let i = 0; i < aDTDs.length; i++) {
|
||||
var id = 'dtd' + i;
|
||||
references += '<!ENTITY % ' + id + ' SYSTEM "' + aDTDs[i] + '">%' + id + ';';
|
||||
}
|
||||
|
||||
var header = '<?xml version="1.0"?><!DOCTYPE elem [' + references + ']>';
|
||||
var element = '<elem id="entity">&' + aEntityId + ';</elem>';
|
||||
var content = header + element;
|
||||
|
||||
var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
|
||||
createInstance(Ci.nsIDOMParser);
|
||||
var doc = parser.parseFromString(content, 'text/xml');
|
||||
var node = doc.querySelector('elem[id="entity"]');
|
||||
|
||||
if (!node) {
|
||||
throw new Error("Unkown entity '" + aEntityId + "'");
|
||||
}
|
||||
|
||||
return node.textContent;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the localized content for a given property
|
||||
*
|
||||
* @memberOf l10n
|
||||
* @param {String} aURL URL of the .properties file.
|
||||
* @param {String} aProperty The property to get the value of.
|
||||
*
|
||||
* @returns {String} Value of the requested property
|
||||
*/
|
||||
function getProperty(aURL, aProperty) {
|
||||
var bundle = Services.strings.createBundle(aURL);
|
||||
|
||||
try {
|
||||
return bundle.GetStringFromName(aProperty);
|
||||
} catch (ex) {
|
||||
throw new Error("Unkown property '" + aProperty + "'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Export of functions
|
||||
l10n.getEntity = getEntity;
|
||||
l10n.getProperty = getProperty;
|
|
@ -1,43 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['findCallerFrame'];
|
||||
|
||||
|
||||
/**
|
||||
* @namespace Defines utility methods for handling stack frames
|
||||
*/
|
||||
|
||||
/**
|
||||
* Find the frame to use for logging the test result. If a start frame has
|
||||
* been specified, we walk down the stack until a frame with the same filename
|
||||
* as the start frame has been found. The next file in the stack will be the
|
||||
* frame to use for logging the result.
|
||||
*
|
||||
* @memberOf stack
|
||||
* @param {Object} [aStartFrame=Components.stack] Frame to start from walking up the stack.
|
||||
* @returns {Object} Frame of the stack to use for logging the result.
|
||||
*/
|
||||
function findCallerFrame(aStartFrame) {
|
||||
let frame = Components.stack;
|
||||
let filename = frame.filename.replace(/(.*)-> /, "");
|
||||
|
||||
// If a start frame has been specified, walk up the stack until we have
|
||||
// found the corresponding file
|
||||
if (aStartFrame) {
|
||||
filename = aStartFrame.filename.replace(/(.*)-> /, "");
|
||||
|
||||
while (frame.caller &&
|
||||
frame.filename && (frame.filename.indexOf(filename) == -1)) {
|
||||
frame = frame.caller;
|
||||
}
|
||||
}
|
||||
|
||||
// Walk even up more until the next file has been found
|
||||
while (frame.caller &&
|
||||
(!frame.filename || (frame.filename.indexOf(filename) != -1)))
|
||||
frame = frame.caller;
|
||||
|
||||
return frame;
|
||||
}
|
|
@ -1,292 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["init", "map"];
|
||||
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
var Cu = Components.utils;
|
||||
|
||||
// imports
|
||||
var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
|
||||
|
||||
var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
|
||||
/**
|
||||
* The window map is used to store information about the current state of
|
||||
* open windows, e.g. loaded state
|
||||
*/
|
||||
var map = {
|
||||
_windows : { },
|
||||
|
||||
/**
|
||||
* Check if a given window id is contained in the map of windows
|
||||
*
|
||||
* @param {Number} aWindowId
|
||||
* Outer ID of the window to check.
|
||||
* @returns {Boolean} True if the window is part of the map, otherwise false.
|
||||
*/
|
||||
contains : function (aWindowId) {
|
||||
return (aWindowId in this._windows);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the value of the specified window's property.
|
||||
*
|
||||
* @param {Number} aWindowId
|
||||
* Outer ID of the window to check.
|
||||
* @param {String} aProperty
|
||||
* Property to retrieve the value from
|
||||
* @return {Object} Value of the window's property
|
||||
*/
|
||||
getValue : function (aWindowId, aProperty) {
|
||||
if (!this.contains(aWindowId)) {
|
||||
return undefined;
|
||||
} else {
|
||||
var win = this._windows[aWindowId];
|
||||
|
||||
return (aProperty in win) ? win[aProperty]
|
||||
: undefined;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the entry for a given window
|
||||
*
|
||||
* @param {Number} aWindowId
|
||||
* Outer ID of the window to check.
|
||||
*/
|
||||
remove : function (aWindowId) {
|
||||
if (this.contains(aWindowId)) {
|
||||
delete this._windows[aWindowId];
|
||||
}
|
||||
|
||||
// dump("* current map: " + JSON.stringify(this._windows) + "\n");
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the property value of a given window
|
||||
*
|
||||
* @param {Number} aWindowId
|
||||
* Outer ID of the window to check.
|
||||
* @param {String} aProperty
|
||||
* Property to update the value for
|
||||
* @param {Object}
|
||||
* Value to set
|
||||
*/
|
||||
update : function (aWindowId, aProperty, aValue) {
|
||||
if (!this.contains(aWindowId)) {
|
||||
this._windows[aWindowId] = { };
|
||||
}
|
||||
|
||||
this._windows[aWindowId][aProperty] = aValue;
|
||||
// dump("* current map: " + JSON.stringify(this._windows) + "\n");
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the internal loaded state of the given content window. To identify
|
||||
* an active (re)load action we make use of an uuid.
|
||||
*
|
||||
* @param {Window} aId - The outer id of the window to update
|
||||
* @param {Boolean} aIsLoaded - Has the window been loaded
|
||||
*/
|
||||
updatePageLoadStatus : function (aId, aIsLoaded) {
|
||||
this.update(aId, "loaded", aIsLoaded);
|
||||
|
||||
var uuid = this.getValue(aId, "id_load_in_transition");
|
||||
|
||||
// If no uuid has been set yet or when the page gets unloaded create a new id
|
||||
if (!uuid || !aIsLoaded) {
|
||||
uuid = uuidgen.generateUUID();
|
||||
this.update(aId, "id_load_in_transition", uuid);
|
||||
}
|
||||
|
||||
// dump("*** Page status updated: id=" + aId + ", loaded=" + aIsLoaded + ", uuid=" + uuid + "\n");
|
||||
},
|
||||
|
||||
/**
|
||||
* This method only applies to content windows, where we have to check if it has
|
||||
* been successfully loaded or reloaded. An uuid allows us to wait for the next
|
||||
* load action triggered by e.g. controller.open().
|
||||
*
|
||||
* @param {Window} aId - The outer id of the content window to check
|
||||
*
|
||||
* @returns {Boolean} True if the content window has been loaded
|
||||
*/
|
||||
hasPageLoaded : function (aId) {
|
||||
var load_current = this.getValue(aId, "id_load_in_transition");
|
||||
var load_handled = this.getValue(aId, "id_load_handled");
|
||||
|
||||
var isLoaded = this.contains(aId) && this.getValue(aId, "loaded") &&
|
||||
(load_current !== load_handled);
|
||||
|
||||
if (isLoaded) {
|
||||
// Backup the current uuid so we can check later if another page load happened.
|
||||
this.update(aId, "id_load_handled", load_current);
|
||||
}
|
||||
|
||||
// dump("** Page has been finished loading: id=" + aId + ", status=" + isLoaded + ", uuid=" + load_current + "\n");
|
||||
|
||||
return isLoaded;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Observer when a new top-level window is ready
|
||||
var windowReadyObserver = {
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
// Not in all cases we get a ChromeWindow. So ensure we really operate
|
||||
// on such an instance. Otherwise load events will not be handled.
|
||||
var win = utils.getChromeWindow(aSubject);
|
||||
|
||||
// var id = utils.getWindowId(win);
|
||||
// dump("*** 'toplevel-window-ready' observer notification: id=" + id + "\n");
|
||||
attachEventListeners(win);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Observer when a top-level window is closed
|
||||
var windowCloseObserver = {
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
var id = utils.getWindowId(aSubject);
|
||||
// dump("*** 'outer-window-destroyed' observer notification: id=" + id + "\n");
|
||||
|
||||
map.remove(id);
|
||||
}
|
||||
};
|
||||
|
||||
// Bug 915554
|
||||
// Support for the old Private Browsing Mode (eg. ESR17)
|
||||
// TODO: remove once ESR17 is no longer supported
|
||||
var enterLeavePrivateBrowsingObserver = {
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
handleAttachEventListeners();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach event listeners
|
||||
*
|
||||
* @param {ChromeWindow} aWindow
|
||||
* Window to attach listeners on.
|
||||
*/
|
||||
function attachEventListeners(aWindow) {
|
||||
// These are the event handlers
|
||||
var pageShowHandler = function (aEvent) {
|
||||
var doc = aEvent.originalTarget;
|
||||
|
||||
// Only update the flag if we have a document as target
|
||||
// see https://bugzilla.mozilla.org/show_bug.cgi?id=690829
|
||||
if ("defaultView" in doc) {
|
||||
var id = utils.getWindowId(doc.defaultView);
|
||||
// dump("*** 'pageshow' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
|
||||
map.updatePageLoadStatus(id, true);
|
||||
}
|
||||
|
||||
// We need to add/remove the unload/pagehide event listeners to preserve caching.
|
||||
aWindow.addEventListener("beforeunload", beforeUnloadHandler, true);
|
||||
aWindow.addEventListener("pagehide", pageHideHandler, true);
|
||||
};
|
||||
|
||||
var DOMContentLoadedHandler = function (aEvent) {
|
||||
var doc = aEvent.originalTarget;
|
||||
|
||||
// Only update the flag if we have a document as target
|
||||
if ("defaultView" in doc) {
|
||||
var id = utils.getWindowId(doc.defaultView);
|
||||
// dump("*** 'DOMContentLoaded' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
|
||||
|
||||
// We only care about error pages for DOMContentLoaded
|
||||
var errorRegex = /about:.+(error)|(blocked)\?/;
|
||||
if (errorRegex.exec(doc.baseURI)) {
|
||||
// Wait about 1s to be sure the DOM is ready
|
||||
utils.sleep(1000);
|
||||
|
||||
map.updatePageLoadStatus(id, true);
|
||||
}
|
||||
|
||||
// We need to add/remove the unload event listener to preserve caching.
|
||||
aWindow.addEventListener("beforeunload", beforeUnloadHandler, true);
|
||||
}
|
||||
};
|
||||
|
||||
// beforeunload is still needed because pagehide doesn't fire before the page is unloaded.
|
||||
// still use pagehide for cases when beforeunload doesn't get fired
|
||||
var beforeUnloadHandler = function (aEvent) {
|
||||
var doc = aEvent.originalTarget;
|
||||
|
||||
// Only update the flag if we have a document as target
|
||||
if ("defaultView" in doc) {
|
||||
var id = utils.getWindowId(doc.defaultView);
|
||||
// dump("*** 'beforeunload' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
|
||||
map.updatePageLoadStatus(id, false);
|
||||
}
|
||||
|
||||
aWindow.removeEventListener("beforeunload", beforeUnloadHandler, true);
|
||||
};
|
||||
|
||||
var pageHideHandler = function (aEvent) {
|
||||
var doc = aEvent.originalTarget;
|
||||
|
||||
// Only update the flag if we have a document as target
|
||||
if ("defaultView" in doc) {
|
||||
var id = utils.getWindowId(doc.defaultView);
|
||||
// dump("*** 'pagehide' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
|
||||
map.updatePageLoadStatus(id, false);
|
||||
}
|
||||
// If event.persisted is true the beforeUnloadHandler would never fire
|
||||
// and we have to remove the event handler here to avoid memory leaks.
|
||||
if (aEvent.persisted)
|
||||
aWindow.removeEventListener("beforeunload", beforeUnloadHandler, true);
|
||||
};
|
||||
|
||||
var onWindowLoaded = function (aEvent) {
|
||||
var id = utils.getWindowId(aWindow);
|
||||
// dump("*** 'load' event: id=" + id + ", baseURI=" + aWindow.document.baseURI + "\n");
|
||||
|
||||
map.update(id, "loaded", true);
|
||||
|
||||
// Note: Error pages will never fire a "pageshow" event. For those we
|
||||
// have to wait for the "DOMContentLoaded" event. That's the final state.
|
||||
// Error pages will always have a baseURI starting with
|
||||
// "about:" followed by "error" or "blocked".
|
||||
aWindow.addEventListener("DOMContentLoaded", DOMContentLoadedHandler, true);
|
||||
|
||||
// Page is ready
|
||||
aWindow.addEventListener("pageshow", pageShowHandler, true);
|
||||
|
||||
// Leave page (use caching)
|
||||
aWindow.addEventListener("pagehide", pageHideHandler, true);
|
||||
};
|
||||
|
||||
// If the window has already been finished loading, call the load handler
|
||||
// directly. Otherwise attach it to the current window.
|
||||
if (aWindow.document.readyState === 'complete') {
|
||||
onWindowLoaded();
|
||||
} else {
|
||||
aWindow.addEventListener("load", onWindowLoaded);
|
||||
}
|
||||
}
|
||||
|
||||
// Attach event listeners to all already open top-level windows
|
||||
function handleAttachEventListeners() {
|
||||
var enumerator = Cc["@mozilla.org/appshell/window-mediator;1"].
|
||||
getService(Ci.nsIWindowMediator).getEnumerator("");
|
||||
while (enumerator.hasMoreElements()) {
|
||||
var win = enumerator.getNext();
|
||||
attachEventListeners(win);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
// Activate observer for new top level windows
|
||||
var observerService = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
observerService.addObserver(windowReadyObserver, "toplevel-window-ready");
|
||||
observerService.addObserver(windowCloseObserver, "outer-window-destroyed");
|
||||
observerService.addObserver(enterLeavePrivateBrowsingObserver, "private-browsing");
|
||||
|
||||
handleAttachEventListeners();
|
||||
}
|
|
@ -1,823 +0,0 @@
|
|||
// Export all available functions for Mozmill
|
||||
var EXPORTED_SYMBOLS = ["disableNonTestMouseEvents","sendMouseEvent", "sendChar",
|
||||
"sendString", "sendKey", "synthesizeMouse", "synthesizeTouch",
|
||||
"synthesizeMouseAtPoint", "synthesizeTouchAtPoint",
|
||||
"synthesizeMouseAtCenter", "synthesizeTouchAtCenter",
|
||||
"synthesizeWheel", "synthesizeKey",
|
||||
"synthesizeMouseExpectEvent", "synthesizeKeyExpectEvent",
|
||||
"synthesizeText",
|
||||
"synthesizeComposition", "synthesizeQuerySelectedText"];
|
||||
|
||||
var Ci = Components.interfaces;
|
||||
var Cc = Components.classes;
|
||||
|
||||
var window = Cc["@mozilla.org/appshell/appShellService;1"]
|
||||
.getService(Ci.nsIAppShellService).hiddenDOMWindow;
|
||||
|
||||
var _EU_Ci = Ci;
|
||||
var navigator = window.navigator;
|
||||
var KeyEvent = window.KeyEvent;
|
||||
var parent = window.parent;
|
||||
|
||||
function is(aExpression1, aExpression2, aMessage) {
|
||||
if (aExpression1 !== aExpression2) {
|
||||
throw new Error(aMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* EventUtils provides some utility methods for creating and sending DOM events.
|
||||
* Current methods:
|
||||
* sendMouseEvent
|
||||
* sendChar
|
||||
* sendString
|
||||
* sendKey
|
||||
* synthesizeMouse
|
||||
* synthesizeMouseAtCenter
|
||||
* synthesizeWheel
|
||||
* synthesizeKey
|
||||
* synthesizeMouseExpectEvent
|
||||
* synthesizeKeyExpectEvent
|
||||
*
|
||||
* When adding methods to this file, please add a performance test for it.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Send a mouse event to the node aTarget (aTarget can be an id, or an
|
||||
* actual node) . The "event" passed in to aEvent is just a JavaScript
|
||||
* object with the properties set that the real mouse event object should
|
||||
* have. This includes the type of the mouse event.
|
||||
* E.g. to send an click event to the node with id 'node' you might do this:
|
||||
*
|
||||
* sendMouseEvent({type:'click'}, 'node');
|
||||
*/
|
||||
function getElement(id) {
|
||||
return ((typeof(id) == "string") ?
|
||||
document.getElementById(id) : id);
|
||||
};
|
||||
|
||||
this.$ = this.getElement;
|
||||
|
||||
function sendMouseEvent(aEvent, aTarget, aWindow) {
|
||||
if (['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) {
|
||||
throw new Error("sendMouseEvent doesn't know about event type '" + aEvent.type + "'");
|
||||
}
|
||||
|
||||
if (!aWindow) {
|
||||
aWindow = window;
|
||||
}
|
||||
|
||||
if (!(aTarget instanceof aWindow.Element)) {
|
||||
aTarget = aWindow.document.getElementById(aTarget);
|
||||
}
|
||||
|
||||
var event = aWindow.document.createEvent('MouseEvent');
|
||||
|
||||
var typeArg = aEvent.type;
|
||||
var canBubbleArg = true;
|
||||
var cancelableArg = true;
|
||||
var viewArg = aWindow;
|
||||
var detailArg = aEvent.detail || (aEvent.type == 'click' ||
|
||||
aEvent.type == 'mousedown' ||
|
||||
aEvent.type == 'mouseup' ? 1 :
|
||||
aEvent.type == 'dblclick'? 2 : 0);
|
||||
var screenXArg = aEvent.screenX || 0;
|
||||
var screenYArg = aEvent.screenY || 0;
|
||||
var clientXArg = aEvent.clientX || 0;
|
||||
var clientYArg = aEvent.clientY || 0;
|
||||
var ctrlKeyArg = aEvent.ctrlKey || false;
|
||||
var altKeyArg = aEvent.altKey || false;
|
||||
var shiftKeyArg = aEvent.shiftKey || false;
|
||||
var metaKeyArg = aEvent.metaKey || false;
|
||||
var buttonArg = aEvent.button || 0;
|
||||
var relatedTargetArg = aEvent.relatedTarget || null;
|
||||
|
||||
event.initMouseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg,
|
||||
screenXArg, screenYArg, clientXArg, clientYArg,
|
||||
ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
|
||||
buttonArg, relatedTargetArg);
|
||||
|
||||
SpecialPowers.dispatchEvent(aWindow, aTarget, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the char aChar to the focused element. This method handles casing of
|
||||
* chars (sends the right charcode, and sends a shift key for uppercase chars).
|
||||
* No other modifiers are handled at this point.
|
||||
*
|
||||
* For now this method only works for ASCII characters and emulates the shift
|
||||
* key state on US keyboard layout.
|
||||
*/
|
||||
function sendChar(aChar, aWindow) {
|
||||
var hasShift;
|
||||
// Emulate US keyboard layout for the shiftKey state.
|
||||
switch (aChar) {
|
||||
case "!":
|
||||
case "@":
|
||||
case "#":
|
||||
case "$":
|
||||
case "%":
|
||||
case "^":
|
||||
case "&":
|
||||
case "*":
|
||||
case "(":
|
||||
case ")":
|
||||
case "_":
|
||||
case "+":
|
||||
case "{":
|
||||
case "}":
|
||||
case ":":
|
||||
case "\"":
|
||||
case "|":
|
||||
case "<":
|
||||
case ">":
|
||||
case "?":
|
||||
hasShift = true;
|
||||
break;
|
||||
default:
|
||||
hasShift = (aChar == aChar.toUpperCase());
|
||||
break;
|
||||
}
|
||||
synthesizeKey(aChar, { shiftKey: hasShift }, aWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the string aStr to the focused element.
|
||||
*
|
||||
* For now this method only works for ASCII characters and emulates the shift
|
||||
* key state on US keyboard layout.
|
||||
*/
|
||||
function sendString(aStr, aWindow) {
|
||||
for (var i = 0; i < aStr.length; ++i) {
|
||||
sendChar(aStr.charAt(i), aWindow);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the non-character key aKey to the focused node.
|
||||
* The name of the key should be the part that comes after "DOM_VK_" in the
|
||||
* KeyEvent constant name for this key.
|
||||
* No modifiers are handled at this point.
|
||||
*/
|
||||
function sendKey(aKey, aWindow) {
|
||||
var keyName = "VK_" + aKey.toUpperCase();
|
||||
synthesizeKey(keyName, { shiftKey: false }, aWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the key modifier flags from aEvent. Used to share code between
|
||||
* synthesizeMouse and synthesizeKey.
|
||||
*/
|
||||
function _parseModifiers(aEvent)
|
||||
{
|
||||
const nsIDOMWindowUtils = _EU_Ci.nsIDOMWindowUtils;
|
||||
var mval = 0;
|
||||
if (aEvent.shiftKey) {
|
||||
mval |= nsIDOMWindowUtils.MODIFIER_SHIFT;
|
||||
}
|
||||
if (aEvent.ctrlKey) {
|
||||
mval |= nsIDOMWindowUtils.MODIFIER_CONTROL;
|
||||
}
|
||||
if (aEvent.altKey) {
|
||||
mval |= nsIDOMWindowUtils.MODIFIER_ALT;
|
||||
}
|
||||
if (aEvent.metaKey) {
|
||||
mval |= nsIDOMWindowUtils.MODIFIER_META;
|
||||
}
|
||||
if (aEvent.accelKey) {
|
||||
mval |= (navigator.platform.indexOf("Mac") >= 0) ?
|
||||
nsIDOMWindowUtils.MODIFIER_META : nsIDOMWindowUtils.MODIFIER_CONTROL;
|
||||
}
|
||||
if (aEvent.altGrKey) {
|
||||
mval |= nsIDOMWindowUtils.MODIFIER_ALTGRAPH;
|
||||
}
|
||||
if (aEvent.capsLockKey) {
|
||||
mval |= nsIDOMWindowUtils.MODIFIER_CAPSLOCK;
|
||||
}
|
||||
if (aEvent.fnKey) {
|
||||
mval |= nsIDOMWindowUtils.MODIFIER_FN;
|
||||
}
|
||||
if (aEvent.numLockKey) {
|
||||
mval |= nsIDOMWindowUtils.MODIFIER_NUMLOCK;
|
||||
}
|
||||
if (aEvent.scrollLockKey) {
|
||||
mval |= nsIDOMWindowUtils.MODIFIER_SCROLLLOCK;
|
||||
}
|
||||
if (aEvent.symbolLockKey) {
|
||||
mval |= nsIDOMWindowUtils.MODIFIER_SYMBOLLOCK;
|
||||
}
|
||||
if (aEvent.osKey) {
|
||||
mval |= nsIDOMWindowUtils.MODIFIER_OS;
|
||||
}
|
||||
|
||||
return mval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a mouse event on a target. The actual client point is determined
|
||||
* by taking the aTarget's client box and offseting it by aOffsetX and
|
||||
* aOffsetY. This allows mouse clicks to be simulated by calling this method.
|
||||
*
|
||||
* aEvent is an object which may contain the properties:
|
||||
* shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
|
||||
*
|
||||
* If the type is specified, an mouse event of that type is fired. Otherwise,
|
||||
* a mousedown followed by a mouse up is performed.
|
||||
*
|
||||
* aWindow is optional, and defaults to the current window object.
|
||||
*
|
||||
* Returns whether the event had preventDefault() called on it.
|
||||
*/
|
||||
function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
|
||||
{
|
||||
var rect = aTarget.getBoundingClientRect();
|
||||
return synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
|
||||
aEvent, aWindow);
|
||||
}
|
||||
function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
|
||||
{
|
||||
var rect = aTarget.getBoundingClientRect();
|
||||
synthesizeTouchAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
|
||||
aEvent, aWindow);
|
||||
}
|
||||
|
||||
/*
|
||||
* Synthesize a mouse event at a particular point in aWindow.
|
||||
*
|
||||
* aEvent is an object which may contain the properties:
|
||||
* shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
|
||||
*
|
||||
* If the type is specified, an mouse event of that type is fired. Otherwise,
|
||||
* a mousedown followed by a mouse up is performed.
|
||||
*
|
||||
* aWindow is optional, and defaults to the current window object.
|
||||
*/
|
||||
function synthesizeMouseAtPoint(left, top, aEvent, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
var defaultPrevented = false;
|
||||
|
||||
if (utils) {
|
||||
var button = aEvent.button || 0;
|
||||
var clickCount = aEvent.clickCount || 1;
|
||||
var modifiers = _parseModifiers(aEvent);
|
||||
var pressure = ("pressure" in aEvent) ? aEvent.pressure : 0;
|
||||
var inputSource = ("inputSource" in aEvent) ? aEvent.inputSource : 0;
|
||||
|
||||
if (("type" in aEvent) && aEvent.type) {
|
||||
defaultPrevented = utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers, false, pressure, inputSource);
|
||||
}
|
||||
else {
|
||||
utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers, false, pressure, inputSource);
|
||||
utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers, false, pressure, inputSource);
|
||||
}
|
||||
}
|
||||
|
||||
return defaultPrevented;
|
||||
}
|
||||
function synthesizeTouchAtPoint(left, top, aEvent, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
|
||||
if (utils) {
|
||||
var id = aEvent.id || 0;
|
||||
var rx = aEvent.rx || 1;
|
||||
var ry = aEvent.rx || 1;
|
||||
var angle = aEvent.angle || 0;
|
||||
var force = aEvent.force || 1;
|
||||
var modifiers = _parseModifiers(aEvent);
|
||||
|
||||
if (("type" in aEvent) && aEvent.type) {
|
||||
utils.sendTouchEvent(aEvent.type, [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
|
||||
}
|
||||
else {
|
||||
utils.sendTouchEvent("touchstart", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
|
||||
utils.sendTouchEvent("touchend", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Call synthesizeMouse with coordinates at the center of aTarget.
|
||||
function synthesizeMouseAtCenter(aTarget, aEvent, aWindow)
|
||||
{
|
||||
var rect = aTarget.getBoundingClientRect();
|
||||
synthesizeMouse(aTarget, rect.width / 2, rect.height / 2, aEvent,
|
||||
aWindow);
|
||||
}
|
||||
function synthesizeTouchAtCenter(aTarget, aEvent, aWindow)
|
||||
{
|
||||
var rect = aTarget.getBoundingClientRect();
|
||||
synthesizeTouch(aTarget, rect.width / 2, rect.height / 2, aEvent,
|
||||
aWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a wheel event on a target. The actual client point is determined
|
||||
* by taking the aTarget's client box and offseting it by aOffsetX and
|
||||
* aOffsetY.
|
||||
*
|
||||
* aEvent is an object which may contain the properties:
|
||||
* shiftKey, ctrlKey, altKey, metaKey, accessKey, deltaX, deltaY, deltaZ,
|
||||
* deltaMode, lineOrPageDeltaX, lineOrPageDeltaY, isMomentum, isPixelOnlyDevice,
|
||||
* isCustomizedByPrefs, expectedOverflowDeltaX, expectedOverflowDeltaY
|
||||
*
|
||||
* deltaMode must be defined, others are ok even if undefined.
|
||||
*
|
||||
* expectedOverflowDeltaX and expectedOverflowDeltaY take integer value. The
|
||||
* value is just checked as 0 or positive or negative.
|
||||
*
|
||||
* aWindow is optional, and defaults to the current window object.
|
||||
*/
|
||||
function synthesizeWheel(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (!utils) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modifiers = _parseModifiers(aEvent);
|
||||
var options = 0;
|
||||
if (aEvent.isPixelOnlyDevice &&
|
||||
(aEvent.deltaMode == WheelEvent.DOM_DELTA_PIXEL)) {
|
||||
options |= utils.WHEEL_EVENT_CAUSED_BY_NO_LINE_OR_PAGE_DELTA_DEVICE;
|
||||
}
|
||||
if (aEvent.isMomentum) {
|
||||
options |= utils.WHEEL_EVENT_CAUSED_BY_MOMENTUM;
|
||||
}
|
||||
if (aEvent.isCustomizedByPrefs) {
|
||||
options |= utils.WHEEL_EVENT_CUSTOMIZED_BY_USER_PREFS;
|
||||
}
|
||||
if (typeof aEvent.expectedOverflowDeltaX !== "undefined") {
|
||||
if (aEvent.expectedOverflowDeltaX === 0) {
|
||||
options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_ZERO;
|
||||
} else if (aEvent.expectedOverflowDeltaX > 0) {
|
||||
options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_POSITIVE;
|
||||
} else {
|
||||
options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_NEGATIVE;
|
||||
}
|
||||
}
|
||||
if (typeof aEvent.expectedOverflowDeltaY !== "undefined") {
|
||||
if (aEvent.expectedOverflowDeltaY === 0) {
|
||||
options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_ZERO;
|
||||
} else if (aEvent.expectedOverflowDeltaY > 0) {
|
||||
options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_POSITIVE;
|
||||
} else {
|
||||
options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_NEGATIVE;
|
||||
}
|
||||
}
|
||||
var isPixelOnlyDevice =
|
||||
aEvent.isPixelOnlyDevice && aEvent.deltaMode == WheelEvent.DOM_DELTA_PIXEL;
|
||||
|
||||
// Avoid the JS warnings "reference to undefined property"
|
||||
if (!aEvent.deltaX) {
|
||||
aEvent.deltaX = 0;
|
||||
}
|
||||
if (!aEvent.deltaY) {
|
||||
aEvent.deltaY = 0;
|
||||
}
|
||||
if (!aEvent.deltaZ) {
|
||||
aEvent.deltaZ = 0;
|
||||
}
|
||||
|
||||
var lineOrPageDeltaX =
|
||||
aEvent.lineOrPageDeltaX != null ? aEvent.lineOrPageDeltaX :
|
||||
aEvent.deltaX > 0 ? Math.floor(aEvent.deltaX) :
|
||||
Math.ceil(aEvent.deltaX);
|
||||
var lineOrPageDeltaY =
|
||||
aEvent.lineOrPageDeltaY != null ? aEvent.lineOrPageDeltaY :
|
||||
aEvent.deltaY > 0 ? Math.floor(aEvent.deltaY) :
|
||||
Math.ceil(aEvent.deltaY);
|
||||
|
||||
var rect = aTarget.getBoundingClientRect();
|
||||
utils.sendWheelEvent(rect.left + aOffsetX, rect.top + aOffsetY,
|
||||
aEvent.deltaX, aEvent.deltaY, aEvent.deltaZ,
|
||||
aEvent.deltaMode, modifiers,
|
||||
lineOrPageDeltaX, lineOrPageDeltaY, options);
|
||||
}
|
||||
|
||||
function _computeKeyCodeFromChar(aChar)
|
||||
{
|
||||
if (aChar.length != 1) {
|
||||
return 0;
|
||||
}
|
||||
const nsIDOMKeyEvent = _EU_Ci.nsIDOMKeyEvent;
|
||||
if (aChar >= 'a' && aChar <= 'z') {
|
||||
return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'a'.charCodeAt(0);
|
||||
}
|
||||
if (aChar >= 'A' && aChar <= 'Z') {
|
||||
return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'A'.charCodeAt(0);
|
||||
}
|
||||
if (aChar >= '0' && aChar <= '9') {
|
||||
return nsIDOMKeyEvent.DOM_VK_0 + aChar.charCodeAt(0) - '0'.charCodeAt(0);
|
||||
}
|
||||
// returns US keyboard layout's keycode
|
||||
switch (aChar) {
|
||||
case '~':
|
||||
case '`':
|
||||
return nsIDOMKeyEvent.DOM_VK_BACK_QUOTE;
|
||||
case '!':
|
||||
return nsIDOMKeyEvent.DOM_VK_1;
|
||||
case '@':
|
||||
return nsIDOMKeyEvent.DOM_VK_2;
|
||||
case '#':
|
||||
return nsIDOMKeyEvent.DOM_VK_3;
|
||||
case '$':
|
||||
return nsIDOMKeyEvent.DOM_VK_4;
|
||||
case '%':
|
||||
return nsIDOMKeyEvent.DOM_VK_5;
|
||||
case '^':
|
||||
return nsIDOMKeyEvent.DOM_VK_6;
|
||||
case '&':
|
||||
return nsIDOMKeyEvent.DOM_VK_7;
|
||||
case '*':
|
||||
return nsIDOMKeyEvent.DOM_VK_8;
|
||||
case '(':
|
||||
return nsIDOMKeyEvent.DOM_VK_9;
|
||||
case ')':
|
||||
return nsIDOMKeyEvent.DOM_VK_0;
|
||||
case '-':
|
||||
case '_':
|
||||
return nsIDOMKeyEvent.DOM_VK_SUBTRACT;
|
||||
case '+':
|
||||
case '=':
|
||||
return nsIDOMKeyEvent.DOM_VK_EQUALS;
|
||||
case '{':
|
||||
case '[':
|
||||
return nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET;
|
||||
case '}':
|
||||
case ']':
|
||||
return nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET;
|
||||
case '|':
|
||||
case '\\':
|
||||
return nsIDOMKeyEvent.DOM_VK_BACK_SLASH;
|
||||
case ':':
|
||||
case ';':
|
||||
return nsIDOMKeyEvent.DOM_VK_SEMICOLON;
|
||||
case '\'':
|
||||
case '"':
|
||||
return nsIDOMKeyEvent.DOM_VK_QUOTE;
|
||||
case '<':
|
||||
case ',':
|
||||
return nsIDOMKeyEvent.DOM_VK_COMMA;
|
||||
case '>':
|
||||
case '.':
|
||||
return nsIDOMKeyEvent.DOM_VK_PERIOD;
|
||||
case '?':
|
||||
case '/':
|
||||
return nsIDOMKeyEvent.DOM_VK_SLASH;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* isKeypressFiredKey() returns TRUE if the given key should cause keypress
|
||||
* event when widget handles the native key event. Otherwise, FALSE.
|
||||
*
|
||||
* aDOMKeyCode should be one of consts of nsIDOMKeyEvent::DOM_VK_*, or a key
|
||||
* name begins with "VK_", or a character.
|
||||
*/
|
||||
function isKeypressFiredKey(aDOMKeyCode)
|
||||
{
|
||||
if (typeof(aDOMKeyCode) == "string") {
|
||||
if (aDOMKeyCode.indexOf("VK_") == 0) {
|
||||
aDOMKeyCode = KeyEvent["DOM_" + aDOMKeyCode];
|
||||
if (!aDOMKeyCode) {
|
||||
throw new Error(`Unknown key: ${aDOMKeyCode}`);
|
||||
}
|
||||
} else {
|
||||
// If the key generates a character, it must cause a keypress event.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
switch (aDOMKeyCode) {
|
||||
case KeyEvent.DOM_VK_SHIFT:
|
||||
case KeyEvent.DOM_VK_CONTROL:
|
||||
case KeyEvent.DOM_VK_ALT:
|
||||
case KeyEvent.DOM_VK_CAPS_LOCK:
|
||||
case KeyEvent.DOM_VK_NUM_LOCK:
|
||||
case KeyEvent.DOM_VK_SCROLL_LOCK:
|
||||
case KeyEvent.DOM_VK_META:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a key event. It is targeted at whatever would be targeted by an
|
||||
* actual keypress by the user, typically the focused element.
|
||||
*
|
||||
* aKey should be either a character or a keycode starting with VK_ such as
|
||||
* VK_ENTER.
|
||||
*
|
||||
* aEvent is an object which may contain the properties:
|
||||
* shiftKey, ctrlKey, altKey, metaKey, accessKey, type, location
|
||||
*
|
||||
* Sets one of KeyboardEvent.DOM_KEY_LOCATION_* to location. Otherwise,
|
||||
* DOMWindowUtils will choose good location from the keycode.
|
||||
*
|
||||
* If the type is specified, a key event of that type is fired. Otherwise,
|
||||
* a keydown, a keypress and then a keyup event are fired in sequence.
|
||||
*
|
||||
* aWindow is optional, and defaults to the current window object.
|
||||
*/
|
||||
function synthesizeKey(aKey, aEvent, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (utils) {
|
||||
var keyCode = 0, charCode = 0;
|
||||
if (aKey.indexOf("VK_") == 0) {
|
||||
keyCode = KeyEvent["DOM_" + aKey];
|
||||
if (!keyCode) {
|
||||
throw new Error(`Unknown key: ${aKey}`);
|
||||
}
|
||||
} else {
|
||||
charCode = aKey.charCodeAt(0);
|
||||
keyCode = _computeKeyCodeFromChar(aKey.charAt(0));
|
||||
}
|
||||
|
||||
var modifiers = _parseModifiers(aEvent);
|
||||
var flags = 0;
|
||||
if (aEvent.location != undefined) {
|
||||
switch (aEvent.location) {
|
||||
case KeyboardEvent.DOM_KEY_LOCATION_STANDARD:
|
||||
flags |= utils.KEY_FLAG_LOCATION_STANDARD;
|
||||
break;
|
||||
case KeyboardEvent.DOM_KEY_LOCATION_LEFT:
|
||||
flags |= utils.KEY_FLAG_LOCATION_LEFT;
|
||||
break;
|
||||
case KeyboardEvent.DOM_KEY_LOCATION_RIGHT:
|
||||
flags |= utils.KEY_FLAG_LOCATION_RIGHT;
|
||||
break;
|
||||
case KeyboardEvent.DOM_KEY_LOCATION_NUMPAD:
|
||||
flags |= utils.KEY_FLAG_LOCATION_NUMPAD;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!("type" in aEvent) || !aEvent.type) {
|
||||
// Send keydown + (optional) keypress + keyup events.
|
||||
var keyDownDefaultHappened =
|
||||
utils.sendKeyEvent("keydown", keyCode, 0, modifiers, flags);
|
||||
if (isKeypressFiredKey(keyCode)) {
|
||||
if (!keyDownDefaultHappened) {
|
||||
flags |= utils.KEY_FLAG_PREVENT_DEFAULT;
|
||||
}
|
||||
utils.sendKeyEvent("keypress", keyCode, charCode, modifiers, flags);
|
||||
}
|
||||
utils.sendKeyEvent("keyup", keyCode, 0, modifiers, flags);
|
||||
} else if (aEvent.type == "keypress") {
|
||||
// Send standalone keypress event.
|
||||
utils.sendKeyEvent(aEvent.type, keyCode, charCode, modifiers, flags);
|
||||
} else {
|
||||
// Send other standalone event than keypress.
|
||||
utils.sendKeyEvent(aEvent.type, keyCode, 0, modifiers, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _gSeenEvent = false;
|
||||
|
||||
/**
|
||||
* Indicate that an event with an original target of aExpectedTarget and
|
||||
* a type of aExpectedEvent is expected to be fired, or not expected to
|
||||
* be fired.
|
||||
*/
|
||||
function _expectEvent(aExpectedTarget, aExpectedEvent, aTestName)
|
||||
{
|
||||
if (!aExpectedTarget || !aExpectedEvent)
|
||||
return null;
|
||||
|
||||
_gSeenEvent = false;
|
||||
|
||||
var type = (aExpectedEvent.charAt(0) == "!") ?
|
||||
aExpectedEvent.substring(1) : aExpectedEvent;
|
||||
var eventHandler = function(event) {
|
||||
var epassed = (!_gSeenEvent && event.originalTarget == aExpectedTarget &&
|
||||
event.type == type);
|
||||
is(epassed, true, aTestName + " " + type + " event target " + (_gSeenEvent ? "twice" : ""));
|
||||
_gSeenEvent = true;
|
||||
};
|
||||
|
||||
aExpectedTarget.addEventListener(type, eventHandler);
|
||||
return eventHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the event was fired or not. The event handler aEventHandler
|
||||
* will be removed.
|
||||
*/
|
||||
function _checkExpectedEvent(aExpectedTarget, aExpectedEvent, aEventHandler, aTestName)
|
||||
{
|
||||
if (aEventHandler) {
|
||||
var expectEvent = (aExpectedEvent.charAt(0) != "!");
|
||||
var type = expectEvent ? aExpectedEvent : aExpectedEvent.substring(1);
|
||||
aExpectedTarget.removeEventListener(type, aEventHandler);
|
||||
var desc = type + " event";
|
||||
if (!expectEvent)
|
||||
desc += " not";
|
||||
is(_gSeenEvent, expectEvent, aTestName + " " + desc + " fired");
|
||||
}
|
||||
|
||||
_gSeenEvent = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to synthesizeMouse except that a test is performed to see if an
|
||||
* event is fired at the right target as a result.
|
||||
*
|
||||
* aExpectedTarget - the expected originalTarget of the event.
|
||||
* aExpectedEvent - the expected type of the event, such as 'select'.
|
||||
* aTestName - the test name when outputing results
|
||||
*
|
||||
* To test that an event is not fired, use an expected type preceded by an
|
||||
* exclamation mark, such as '!select'. This might be used to test that a
|
||||
* click on a disabled element doesn't fire certain events for instance.
|
||||
*
|
||||
* aWindow is optional, and defaults to the current window object.
|
||||
*/
|
||||
function synthesizeMouseExpectEvent(aTarget, aOffsetX, aOffsetY, aEvent,
|
||||
aExpectedTarget, aExpectedEvent, aTestName,
|
||||
aWindow)
|
||||
{
|
||||
var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
|
||||
synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow);
|
||||
_checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to synthesizeKey except that a test is performed to see if an
|
||||
* event is fired at the right target as a result.
|
||||
*
|
||||
* aExpectedTarget - the expected originalTarget of the event.
|
||||
* aExpectedEvent - the expected type of the event, such as 'select'.
|
||||
* aTestName - the test name when outputing results
|
||||
*
|
||||
* To test that an event is not fired, use an expected type preceded by an
|
||||
* exclamation mark, such as '!select'.
|
||||
*
|
||||
* aWindow is optional, and defaults to the current window object.
|
||||
*/
|
||||
function synthesizeKeyExpectEvent(key, aEvent, aExpectedTarget, aExpectedEvent,
|
||||
aTestName, aWindow)
|
||||
{
|
||||
var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
|
||||
synthesizeKey(key, aEvent, aWindow);
|
||||
_checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
|
||||
}
|
||||
|
||||
function disableNonTestMouseEvents(aDisable)
|
||||
{
|
||||
var domutils = _getDOMWindowUtils();
|
||||
domutils.disableNonTestMouseEvents(aDisable);
|
||||
}
|
||||
|
||||
function _getDOMWindowUtils(aWindow)
|
||||
{
|
||||
if (!aWindow) {
|
||||
aWindow = window;
|
||||
}
|
||||
|
||||
// we need parent.SpecialPowers for:
|
||||
// layout/base/tests/test_reftests_with_caret.html
|
||||
// chrome: toolkit/content/tests/chrome/test_findbar.xul
|
||||
// chrome: toolkit/content/tests/chrome/test_popup_anchor.xul
|
||||
if ("SpecialPowers" in window && window.SpecialPowers != undefined) {
|
||||
return SpecialPowers.getDOMWindowUtils(aWindow);
|
||||
}
|
||||
if ("SpecialPowers" in parent && parent.SpecialPowers != undefined) {
|
||||
return parent.SpecialPowers.getDOMWindowUtils(aWindow);
|
||||
}
|
||||
|
||||
//TODO: this is assuming we are in chrome space
|
||||
return aWindow.QueryInterface(_EU_Ci.nsIInterfaceRequestor).
|
||||
getInterface(_EU_Ci.nsIDOMWindowUtils);
|
||||
}
|
||||
|
||||
// Must be synchronized with nsIDOMWindowUtils.
|
||||
const COMPOSITION_ATTR_RAWINPUT = 0x02;
|
||||
const COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03;
|
||||
const COMPOSITION_ATTR_CONVERTEDTEXT = 0x04;
|
||||
const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;
|
||||
|
||||
/**
|
||||
* Synthesize a composition event.
|
||||
*
|
||||
* @param aEvent The composition event information. This must
|
||||
* have |type| member. The value must be
|
||||
* "compositionstart", "compositionend" or
|
||||
* "compositionupdate".
|
||||
* And also this may have |data| and |locale| which
|
||||
* would be used for the value of each property of
|
||||
* the composition event. Note that the data would
|
||||
* be ignored if the event type were
|
||||
* "compositionstart".
|
||||
* @param aWindow Optional (If null, current |window| will be used)
|
||||
*/
|
||||
function synthesizeComposition(aEvent, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (!utils) {
|
||||
return;
|
||||
}
|
||||
|
||||
utils.sendCompositionEvent(aEvent.type, aEvent.data ? aEvent.data : "",
|
||||
aEvent.locale ? aEvent.locale : "");
|
||||
}
|
||||
/**
|
||||
* Synthesize a text event.
|
||||
*
|
||||
* @param aEvent The text event's information, this has |composition|
|
||||
* and |caret| members. |composition| has |string| and
|
||||
* |clauses| members. |clauses| must be array object. Each
|
||||
* object has |length| and |attr|. And |caret| has |start| and
|
||||
* |length|. See the following tree image.
|
||||
*
|
||||
* aEvent
|
||||
* +-- composition
|
||||
* | +-- string
|
||||
* | +-- clauses[]
|
||||
* | +-- length
|
||||
* | +-- attr
|
||||
* +-- caret
|
||||
* +-- start
|
||||
* +-- length
|
||||
*
|
||||
* Set the composition string to |composition.string|. Set its
|
||||
* clauses information to the |clauses| array.
|
||||
*
|
||||
* When it's composing, set the each clauses' length to the
|
||||
* |composition.clauses[n].length|. The sum of the all length
|
||||
* values must be same as the length of |composition.string|.
|
||||
* Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the
|
||||
* |composition.clauses[n].attr|.
|
||||
*
|
||||
* When it's not composing, set 0 to the
|
||||
* |composition.clauses[0].length| and
|
||||
* |composition.clauses[0].attr|.
|
||||
*
|
||||
* Set caret position to the |caret.start|. It's offset from
|
||||
* the start of the composition string. Set caret length to
|
||||
* |caret.length|. If it's larger than 0, it should be wide
|
||||
* caret. However, current nsEditor doesn't support wide
|
||||
* caret, therefore, you should always set 0 now.
|
||||
*
|
||||
* @param aWindow Optional (If null, current |window| will be used)
|
||||
*/
|
||||
function synthesizeText(aEvent, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (!utils) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aEvent.composition || !aEvent.composition.clauses ||
|
||||
!aEvent.composition.clauses[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var firstClauseLength = aEvent.composition.clauses[0].length;
|
||||
var firstClauseAttr = aEvent.composition.clauses[0].attr;
|
||||
var secondClauseLength = 0;
|
||||
var secondClauseAttr = 0;
|
||||
var thirdClauseLength = 0;
|
||||
var thirdClauseAttr = 0;
|
||||
if (aEvent.composition.clauses[1]) {
|
||||
secondClauseLength = aEvent.composition.clauses[1].length;
|
||||
secondClauseAttr = aEvent.composition.clauses[1].attr;
|
||||
if (aEvent.composition.clauses[2]) {
|
||||
thirdClauseLength = aEvent.composition.clauses[2].length;
|
||||
thirdClauseAttr = aEvent.composition.clauses[2].attr;
|
||||
}
|
||||
}
|
||||
|
||||
var caretStart = -1;
|
||||
var caretLength = 0;
|
||||
if (aEvent.caret) {
|
||||
caretStart = aEvent.caret.start;
|
||||
caretLength = aEvent.caret.length;
|
||||
}
|
||||
|
||||
utils.sendTextEvent(aEvent.composition.string,
|
||||
firstClauseLength, firstClauseAttr,
|
||||
secondClauseLength, secondClauseAttr,
|
||||
thirdClauseLength, thirdClauseAttr,
|
||||
caretStart, caretLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a query selected text event.
|
||||
*
|
||||
* @param aWindow Optional (If null, current |window| will be used)
|
||||
* @return An nsIQueryContentEventResult object. If this failed,
|
||||
* the result might be null.
|
||||
*/
|
||||
function synthesizeQuerySelectedText(aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (!utils) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return utils.sendQueryContentEvent(utils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['inArray', 'getSet', 'indexOf',
|
||||
'remove', 'rindexOf', 'compare'];
|
||||
|
||||
|
||||
function remove(array, from, to) {
|
||||
var rest = array.slice((to || from) + 1 || array.length);
|
||||
array.length = from < 0 ? array.length + from : from;
|
||||
|
||||
return array.push.apply(array, rest);
|
||||
}
|
||||
|
||||
function inArray(array, value) {
|
||||
for (var i in array) {
|
||||
if (value == array[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getSet(array) {
|
||||
var narray = [];
|
||||
|
||||
for (var i in array) {
|
||||
if (!inArray(narray, array[i])) {
|
||||
narray.push(array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return narray;
|
||||
}
|
||||
|
||||
function indexOf(array, v, offset) {
|
||||
for (var i in array) {
|
||||
if (offset == undefined || i >= offset) {
|
||||
if (!isNaN(i) && array[i] == v) {
|
||||
return new Number(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
function rindexOf (array, v) {
|
||||
var l = array.length;
|
||||
|
||||
for (var i in array) {
|
||||
if (!isNaN(i)) {
|
||||
var i = new Number(i);
|
||||
}
|
||||
|
||||
if (!isNaN(i) && array[l - i] == v) {
|
||||
return l - i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
function compare (array, carray) {
|
||||
if (array.length != carray.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i in array) {
|
||||
if (array[i] != carray[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['getAttributes'];
|
||||
|
||||
|
||||
var getAttributes = function (node) {
|
||||
var attributes = {};
|
||||
|
||||
for (var i in node.attributes) {
|
||||
if (!isNaN(i)) {
|
||||
try {
|
||||
var attr = node.attributes[i];
|
||||
attributes[attr.name] = attr.value;
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,469 +0,0 @@
|
|||
/*
|
||||
http://www.JSON.org/json2.js
|
||||
2008-05-25
|
||||
|
||||
Public Domain.
|
||||
|
||||
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
|
||||
|
||||
See http://www.JSON.org/js.html
|
||||
|
||||
This file creates a global JSON object containing two methods: stringify
|
||||
and parse.
|
||||
|
||||
JSON.stringify(value, replacer, space)
|
||||
value any JavaScript value, usually an object or array.
|
||||
|
||||
replacer an optional parameter that determines how object
|
||||
values are stringified for objects without a toJSON
|
||||
method. It can be a function or an array.
|
||||
|
||||
space an optional parameter that specifies the indentation
|
||||
of nested structures. If it is omitted, the text will
|
||||
be packed without extra whitespace. If it is a number,
|
||||
it will specify the number of spaces to indent at each
|
||||
level. If it is a string (such as '\t' or ' '),
|
||||
it contains the characters used to indent at each level.
|
||||
|
||||
This method produces a JSON text from a JavaScript value.
|
||||
|
||||
When an object value is found, if the object contains a toJSON
|
||||
method, its toJSON method will be called and the result will be
|
||||
stringified. A toJSON method does not serialize: it returns the
|
||||
value represented by the name/value pair that should be serialized,
|
||||
or undefined if nothing should be serialized. The toJSON method
|
||||
will be passed the key associated with the value, and this will be
|
||||
bound to the object holding the key.
|
||||
|
||||
For example, this would serialize Dates as ISO strings.
|
||||
|
||||
Date.prototype.toJSON = function (key) {
|
||||
function f(n) {
|
||||
// Format integers to have at least two digits.
|
||||
return n < 10 ? '0' + n : n;
|
||||
}
|
||||
|
||||
return this.getUTCFullYear() + '-' +
|
||||
f(this.getUTCMonth() + 1) + '-' +
|
||||
f(this.getUTCDate()) + 'T' +
|
||||
f(this.getUTCHours()) + ':' +
|
||||
f(this.getUTCMinutes()) + ':' +
|
||||
f(this.getUTCSeconds()) + 'Z';
|
||||
};
|
||||
|
||||
You can provide an optional replacer method. It will be passed the
|
||||
key and value of each member, with this bound to the containing
|
||||
object. The value that is returned from your method will be
|
||||
serialized. If your method returns undefined, then the member will
|
||||
be excluded from the serialization.
|
||||
|
||||
If the replacer parameter is an array, then it will be used to
|
||||
select the members to be serialized. It filters the results such
|
||||
that only members with keys listed in the replacer array are
|
||||
stringified.
|
||||
|
||||
Values that do not have JSON representations, such as undefined or
|
||||
functions, will not be serialized. Such values in objects will be
|
||||
dropped; in arrays they will be replaced with null. You can use
|
||||
a replacer function to replace those with JSON values.
|
||||
JSON.stringify(undefined) returns undefined.
|
||||
|
||||
The optional space parameter produces a stringification of the
|
||||
value that is filled with line breaks and indentation to make it
|
||||
easier to read.
|
||||
|
||||
If the space parameter is a non-empty string, then that string will
|
||||
be used for indentation. If the space parameter is a number, then
|
||||
the indentation will be that many spaces.
|
||||
|
||||
Example:
|
||||
|
||||
text = JSON.stringify(['e', {pluribus: 'unum'}]);
|
||||
// text is '["e",{"pluribus":"unum"}]'
|
||||
|
||||
|
||||
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
|
||||
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
|
||||
|
||||
text = JSON.stringify([new Date()], function (key, value) {
|
||||
return this[key] instanceof Date ?
|
||||
'Date(' + this[key] + ')' : value;
|
||||
});
|
||||
// text is '["Date(---current time---)"]'
|
||||
|
||||
|
||||
JSON.parse(text, reviver)
|
||||
This method parses a JSON text to produce an object or array.
|
||||
It can throw a SyntaxError exception.
|
||||
|
||||
The optional reviver parameter is a function that can filter and
|
||||
transform the results. It receives each of the keys and values,
|
||||
and its return value is used instead of the original value.
|
||||
If it returns what it received, then the structure is not modified.
|
||||
If it returns undefined then the member is deleted.
|
||||
|
||||
Example:
|
||||
|
||||
// Parse the text. Values that look like ISO date strings will
|
||||
// be converted to Date objects.
|
||||
|
||||
myData = JSON.parse(text, function (key, value) {
|
||||
var a;
|
||||
if (typeof value === 'string') {
|
||||
a =
|
||||
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
|
||||
if (a) {
|
||||
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
|
||||
+a[5], +a[6]));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
|
||||
var d;
|
||||
if (typeof value === 'string' &&
|
||||
value.slice(0, 5) === 'Date(' &&
|
||||
value.slice(-1) === ')') {
|
||||
d = new Date(value.slice(5, -1));
|
||||
if (d) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
|
||||
This is a reference implementation. You are free to copy, modify, or
|
||||
redistribute.
|
||||
|
||||
This code should be minified before deployment.
|
||||
See http://javascript.crockford.com/jsmin.html
|
||||
|
||||
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
|
||||
NOT CONTROL.
|
||||
*/
|
||||
|
||||
/*jslint evil: true */
|
||||
|
||||
/*global JSON */
|
||||
|
||||
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", call,
|
||||
charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes,
|
||||
getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length,
|
||||
parse, propertyIsEnumerable, prototype, push, replace, slice, stringify,
|
||||
test, toJSON, toString
|
||||
*/
|
||||
|
||||
var EXPORTED_SYMBOLS = ["JSON"];
|
||||
|
||||
// Create a JSON object only if one does not already exist. We create the
|
||||
// object in a closure to avoid creating global variables.
|
||||
|
||||
JSON = function () {
|
||||
|
||||
function f(n) {
|
||||
// Format integers to have at least two digits.
|
||||
return n < 10 ? '0' + n : n;
|
||||
}
|
||||
|
||||
Date.prototype.toJSON = function (key) {
|
||||
|
||||
return this.getUTCFullYear() + '-' +
|
||||
f(this.getUTCMonth() + 1) + '-' +
|
||||
f(this.getUTCDate()) + 'T' +
|
||||
f(this.getUTCHours()) + ':' +
|
||||
f(this.getUTCMinutes()) + ':' +
|
||||
f(this.getUTCSeconds()) + 'Z';
|
||||
};
|
||||
|
||||
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||||
escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||||
gap,
|
||||
indent,
|
||||
meta = { // table of character substitutions
|
||||
'\b': '\\b',
|
||||
'\t': '\\t',
|
||||
'\n': '\\n',
|
||||
'\f': '\\f',
|
||||
'\r': '\\r',
|
||||
'"' : '\\"',
|
||||
'\\': '\\\\'
|
||||
},
|
||||
rep;
|
||||
|
||||
|
||||
function quote(string) {
|
||||
|
||||
// If the string contains no control characters, no quote characters, and no
|
||||
// backslash characters, then we can safely slap some quotes around it.
|
||||
// Otherwise we must also replace the offending characters with safe escape
|
||||
// sequences.
|
||||
|
||||
escapeable.lastIndex = 0;
|
||||
return escapeable.test(string) ?
|
||||
'"' + string.replace(escapeable, function (a) {
|
||||
var c = meta[a];
|
||||
if (typeof c === 'string') {
|
||||
return c;
|
||||
}
|
||||
return '\\u' + ('0000' +
|
||||
(+(a.charCodeAt(0))).toString(16)).slice(-4);
|
||||
}) + '"' :
|
||||
'"' + string + '"';
|
||||
}
|
||||
|
||||
|
||||
function str(key, holder) {
|
||||
|
||||
// Produce a string from holder[key].
|
||||
|
||||
var i, // The loop counter.
|
||||
k, // The member key.
|
||||
v, // The member value.
|
||||
length,
|
||||
mind = gap,
|
||||
partial,
|
||||
value = holder[key];
|
||||
|
||||
// If the value has a toJSON method, call it to obtain a replacement value.
|
||||
|
||||
if (value && typeof value === 'object' &&
|
||||
typeof value.toJSON === 'function') {
|
||||
value = value.toJSON(key);
|
||||
}
|
||||
|
||||
// If we were called with a replacer function, then call the replacer to
|
||||
// obtain a replacement value.
|
||||
|
||||
if (typeof rep === 'function') {
|
||||
value = rep.call(holder, key, value);
|
||||
}
|
||||
|
||||
// What happens next depends on the value's type.
|
||||
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
return quote(value);
|
||||
|
||||
case 'number':
|
||||
|
||||
// JSON numbers must be finite. Encode non-finite numbers as null.
|
||||
|
||||
return isFinite(value) ? String(value) : 'null';
|
||||
|
||||
case 'boolean':
|
||||
case 'null':
|
||||
|
||||
// If the value is a boolean or null, convert it to a string. Note:
|
||||
// typeof null does not produce 'null'. The case is included here in
|
||||
// the remote chance that this gets fixed someday.
|
||||
|
||||
return String(value);
|
||||
|
||||
// If the type is 'object', we might be dealing with an object or an array or
|
||||
// null.
|
||||
|
||||
case 'object':
|
||||
|
||||
// Due to a specification blunder in ECMAScript, typeof null is 'object',
|
||||
// so watch out for that case.
|
||||
|
||||
if (!value) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
// Make an array to hold the partial results of stringifying this object value.
|
||||
|
||||
gap += indent;
|
||||
partial = [];
|
||||
|
||||
// If the object has a dontEnum length property, we'll treat it as an array.
|
||||
|
||||
if (typeof value.length === 'number' &&
|
||||
!(value.propertyIsEnumerable('length'))) {
|
||||
|
||||
// The object is an array. Stringify every element. Use null as a placeholder
|
||||
// for non-JSON values.
|
||||
|
||||
length = value.length;
|
||||
for (i = 0; i < length; i += 1) {
|
||||
partial[i] = str(i, value) || 'null';
|
||||
}
|
||||
|
||||
// Join all of the elements together, separated with commas, and wrap them in
|
||||
// brackets.
|
||||
|
||||
v = partial.length === 0 ? '[]' :
|
||||
gap ? '[\n' + gap +
|
||||
partial.join(',\n' + gap) + '\n' +
|
||||
mind + ']' :
|
||||
'[' + partial.join(',') + ']';
|
||||
gap = mind;
|
||||
return v;
|
||||
}
|
||||
|
||||
// If the replacer is an array, use it to select the members to be stringified.
|
||||
|
||||
if (rep && typeof rep === 'object') {
|
||||
length = rep.length;
|
||||
for (i = 0; i < length; i += 1) {
|
||||
k = rep[i];
|
||||
if (typeof k === 'string') {
|
||||
v = str(k, value, rep);
|
||||
if (v) {
|
||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Otherwise, iterate through all of the keys in the object.
|
||||
|
||||
for (k in value) {
|
||||
if (Object.hasOwnProperty.call(value, k)) {
|
||||
v = str(k, value, rep);
|
||||
if (v) {
|
||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Join all of the member texts together, separated with commas,
|
||||
// and wrap them in braces.
|
||||
|
||||
v = partial.length === 0 ? '{}' :
|
||||
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
|
||||
mind + '}' : '{' + partial.join(',') + '}';
|
||||
gap = mind;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the JSON object containing the stringify and parse methods.
|
||||
|
||||
return {
|
||||
stringify: function (value, replacer, space) {
|
||||
|
||||
// The stringify method takes a value and an optional replacer, and an optional
|
||||
// space parameter, and returns a JSON text. The replacer can be a function
|
||||
// that can replace values, or an array of strings that will select the keys.
|
||||
// A default replacer method can be provided. Use of the space parameter can
|
||||
// produce text that is more easily readable.
|
||||
|
||||
var i;
|
||||
gap = '';
|
||||
indent = '';
|
||||
|
||||
// If the space parameter is a number, make an indent string containing that
|
||||
// many spaces.
|
||||
|
||||
if (typeof space === 'number') {
|
||||
for (i = 0; i < space; i += 1) {
|
||||
indent += ' ';
|
||||
}
|
||||
|
||||
// If the space parameter is a string, it will be used as the indent string.
|
||||
|
||||
} else if (typeof space === 'string') {
|
||||
indent = space;
|
||||
}
|
||||
|
||||
// If there is a replacer, it must be a function or an array.
|
||||
// Otherwise, throw an error.
|
||||
|
||||
rep = replacer;
|
||||
if (replacer && typeof replacer !== 'function' &&
|
||||
(typeof replacer !== 'object' ||
|
||||
typeof replacer.length !== 'number')) {
|
||||
throw new Error('JSON.stringify');
|
||||
}
|
||||
|
||||
// Make a fake root object containing our value under the key of ''.
|
||||
// Return the result of stringifying the value.
|
||||
|
||||
return str('', {'': value});
|
||||
},
|
||||
|
||||
|
||||
parse: function (text, reviver) {
|
||||
|
||||
// The parse method takes a text and an optional reviver function, and returns
|
||||
// a JavaScript value if the text is a valid JSON text.
|
||||
|
||||
var j;
|
||||
|
||||
function walk(holder, key) {
|
||||
|
||||
// The walk method is used to recursively walk the resulting structure so
|
||||
// that modifications can be made.
|
||||
|
||||
var k, v, value = holder[key];
|
||||
if (value && typeof value === 'object') {
|
||||
for (k in value) {
|
||||
if (Object.hasOwnProperty.call(value, k)) {
|
||||
v = walk(value, k);
|
||||
if (v !== undefined) {
|
||||
value[k] = v;
|
||||
} else {
|
||||
delete value[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reviver.call(holder, key, value);
|
||||
}
|
||||
|
||||
|
||||
// Parsing happens in four stages. In the first stage, we replace certain
|
||||
// Unicode characters with escape sequences. JavaScript handles many characters
|
||||
// incorrectly, either silently deleting them, or treating them as line endings.
|
||||
|
||||
cx.lastIndex = 0;
|
||||
if (cx.test(text)) {
|
||||
text = text.replace(cx, function (a) {
|
||||
return '\\u' + ('0000' +
|
||||
(+(a.charCodeAt(0))).toString(16)).slice(-4);
|
||||
});
|
||||
}
|
||||
|
||||
// In the second stage, we run the text against regular expressions that look
|
||||
// for non-JSON patterns. We are especially concerned with '()' and 'new'
|
||||
// because they can cause invocation, and '=' because it can cause mutation.
|
||||
// But just to be safe, we want to reject all unexpected forms.
|
||||
|
||||
// We split the second stage into 4 regexp operations in order to work around
|
||||
// crippling inefficiencies in IE's and Safari's regexp engines. First we
|
||||
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
|
||||
// replace all simple value tokens with ']' characters. Third, we delete all
|
||||
// open brackets that follow a colon or comma or that begin the text. Finally,
|
||||
// we look to see that the remaining characters are only whitespace or ']' or
|
||||
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
|
||||
|
||||
if (/^[\],:{}\s]*$/.
|
||||
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
|
||||
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
|
||||
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
|
||||
|
||||
// In the third stage we use the eval function to compile the text into a
|
||||
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
|
||||
// in JavaScript: it can begin a block or an object literal. We wrap the text
|
||||
// in parens to eliminate the ambiguity.
|
||||
|
||||
j = eval('(' + text + ')');
|
||||
|
||||
// In the optional fourth stage, we recursively walk the new structure, passing
|
||||
// each name/value pair to a reviver function for possible transformation.
|
||||
|
||||
return typeof reviver === 'function' ?
|
||||
walk({'': j}, '') : j;
|
||||
}
|
||||
|
||||
// If the text is not JSON parseable, then a SyntaxError is thrown.
|
||||
|
||||
throw new SyntaxError('JSON.parse');
|
||||
}
|
||||
};
|
||||
}();
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['getLength', ];//'compare'];
|
||||
|
||||
var getLength = function (obj) {
|
||||
var len = 0;
|
||||
for (let i in obj) {
|
||||
len++;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// var logging = {}; Components.utils.import('resource://mozmill/stdlib/logging.js', logging);
|
||||
|
||||
// var objectsLogger = logging.getLogger('objectsLogger');
|
||||
|
||||
// var compare = function (obj1, obj2, depth, recursion) {
|
||||
// if (depth == undefined) {
|
||||
// var depth = 4;
|
||||
// }
|
||||
// if (recursion == undefined) {
|
||||
// var recursion = 0;
|
||||
// }
|
||||
//
|
||||
// if (recursion > depth) {
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// if (typeof(obj1) != typeof(obj2)) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (typeof(obj1) == "object" && typeof(obj2) == "object") {
|
||||
// if ([x for (x in obj1)].length != [x for (x in obj2)].length) {
|
||||
// return false;
|
||||
// }
|
||||
// for (i in obj1) {
|
||||
// recursion++;
|
||||
// var result = compare(obj1[i], obj2[i], depth, recursion);
|
||||
// objectsLogger.info(i+' in recursion '+result);
|
||||
// if (result == false) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// if (obj1 != obj2) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// return true;
|
||||
// }
|
|
@ -1,57 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['listDirectory', 'getFileForPath', 'abspath', 'getPlatform'];
|
||||
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
var Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function listDirectory(file) {
|
||||
// file is the given directory (nsIFile)
|
||||
var entries = file.directoryEntries;
|
||||
var array = [];
|
||||
|
||||
while (entries.hasMoreElements()) {
|
||||
var entry = entries.getNext();
|
||||
entry.QueryInterface(Ci.nsIFile);
|
||||
array.push(entry);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
function getFileForPath(path) {
|
||||
var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
file.initWithPath(path);
|
||||
return file;
|
||||
}
|
||||
|
||||
function abspath(rel, file) {
|
||||
var relSplit = rel.split('/');
|
||||
|
||||
if (relSplit[0] == '..' && !file.isDirectory()) {
|
||||
file = file.parent;
|
||||
}
|
||||
|
||||
for (var p of relSplit) {
|
||||
if (p == '..') {
|
||||
file = file.parent;
|
||||
} else if (p == '.') {
|
||||
if (!file.isDirectory()) {
|
||||
file = file.parent;
|
||||
}
|
||||
} else {
|
||||
file.append(p);
|
||||
}
|
||||
}
|
||||
|
||||
return file.path;
|
||||
}
|
||||
|
||||
function getPlatform() {
|
||||
return Services.appinfo.OS.toLowerCase();
|
||||
}
|
|
@ -1,370 +0,0 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Jetpack.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2007
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Atul Varma <atul@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
(function(global) {
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
var exports = {};
|
||||
|
||||
var ios = Cc['@mozilla.org/network/io-service;1']
|
||||
.getService(Ci.nsIIOService);
|
||||
|
||||
var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
|
||||
.createInstance(Ci.nsIPrincipal);
|
||||
|
||||
function resolvePrincipal(principal, defaultPrincipal) {
|
||||
if (principal === undefined)
|
||||
return defaultPrincipal;
|
||||
if (principal == "system")
|
||||
return systemPrincipal;
|
||||
return principal;
|
||||
}
|
||||
|
||||
// The base URI to we use when we're given relative URLs, if any.
|
||||
var baseURI = null;
|
||||
if (global.window)
|
||||
baseURI = ios.newURI(global.location.href);
|
||||
exports.baseURI = baseURI;
|
||||
|
||||
// The "parent" chrome URI to use if we're loading code that
|
||||
// needs chrome privileges but may not have a filename that
|
||||
// matches any of SpiderMonkey's defined system filename prefixes.
|
||||
// The latter is needed so that wrappers can be automatically
|
||||
// made for the code. For more information on this, see
|
||||
// bug 418356:
|
||||
//
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=418356
|
||||
var parentChromeURIString;
|
||||
if (baseURI)
|
||||
// We're being loaded from a chrome-privileged document, so
|
||||
// use its URL as the parent string.
|
||||
parentChromeURIString = baseURI.spec;
|
||||
else
|
||||
// We're being loaded from a chrome-privileged JS module or
|
||||
// SecurableModule, so use its filename (which may itself
|
||||
// contain a reference to a parent).
|
||||
parentChromeURIString = Components.stack.filename;
|
||||
|
||||
function maybeParentifyFilename(filename) {
|
||||
var doParentifyFilename = true;
|
||||
try {
|
||||
// TODO: Ideally we should just make
|
||||
// nsIChromeRegistry.wrappersEnabled() available from script
|
||||
// and use it here. Until that's in the platform, though,
|
||||
// we'll play it safe and parentify the filename unless
|
||||
// we're absolutely certain things will be ok if we don't.
|
||||
var filenameURI = ios.newURI(filename,
|
||||
null,
|
||||
baseURI);
|
||||
if (filenameURI.scheme == 'chrome' &&
|
||||
filenameURI.pathQueryRef.indexOf('/content/') == 0)
|
||||
// Content packages will always have wrappers made for them;
|
||||
// if automatic wrappers have been disabled for the
|
||||
// chrome package via a chrome manifest flag, then
|
||||
// this still works too, to the extent that the
|
||||
// content package is insecure anyways.
|
||||
doParentifyFilename = false;
|
||||
} catch (e) {}
|
||||
if (doParentifyFilename)
|
||||
return parentChromeURIString + " -> " + filename;
|
||||
return filename;
|
||||
}
|
||||
|
||||
function getRootDir(urlStr) {
|
||||
// TODO: This feels hacky, and like there will be edge cases.
|
||||
return urlStr.slice(0, urlStr.lastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
exports.SandboxFactory = function SandboxFactory(defaultPrincipal) {
|
||||
// Unless specified otherwise, use a principal with limited
|
||||
// privileges.
|
||||
this._defaultPrincipal = resolvePrincipal(defaultPrincipal,
|
||||
"http://www.mozilla.org");
|
||||
},
|
||||
|
||||
exports.SandboxFactory.prototype = {
|
||||
createSandbox: function createSandbox(options) {
|
||||
var principal = resolvePrincipal(options.principal,
|
||||
this._defaultPrincipal);
|
||||
|
||||
return {
|
||||
_sandbox: new Cu.Sandbox(principal),
|
||||
_principal: principal,
|
||||
get globalScope() {
|
||||
return this._sandbox;
|
||||
},
|
||||
defineProperty: function defineProperty(name, value) {
|
||||
this._sandbox[name] = value;
|
||||
},
|
||||
getProperty: function getProperty(name) {
|
||||
return this._sandbox[name];
|
||||
},
|
||||
evaluate: function evaluate(options) {
|
||||
if (typeof(options) == 'string')
|
||||
options = {contents: options};
|
||||
options = {__proto__: options};
|
||||
if (typeof(options.contents) != 'string')
|
||||
throw new Error('Expected string for options.contents');
|
||||
if (options.lineNo === undefined)
|
||||
options.lineNo = 1;
|
||||
if (options.jsVersion === undefined)
|
||||
options.jsVersion = "1.8";
|
||||
if (typeof(options.filename) != 'string')
|
||||
options.filename = '<string>';
|
||||
|
||||
if (this._principal == systemPrincipal)
|
||||
options.filename = maybeParentifyFilename(options.filename);
|
||||
|
||||
return Cu.evalInSandbox(options.contents,
|
||||
this._sandbox,
|
||||
options.jsVersion,
|
||||
options.filename,
|
||||
options.lineNo);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
exports.Loader = function Loader(options) {
|
||||
options = {__proto__: options};
|
||||
if (options.fs === undefined) {
|
||||
var rootPaths = options.rootPath || options.rootPaths;
|
||||
if (rootPaths) {
|
||||
if (rootPaths.constructor.name != "Array")
|
||||
rootPaths = [rootPaths];
|
||||
var fses = rootPaths.map(path => new exports.LocalFileSystem(path));
|
||||
options.fs = new exports.CompositeFileSystem(fses);
|
||||
} else
|
||||
options.fs = new exports.LocalFileSystem();
|
||||
}
|
||||
if (options.sandboxFactory === undefined)
|
||||
options.sandboxFactory = new exports.SandboxFactory(
|
||||
options.defaultPrincipal
|
||||
);
|
||||
if (options.modules === undefined)
|
||||
options.modules = {};
|
||||
if (options.globals === undefined)
|
||||
options.globals = {};
|
||||
|
||||
this.fs = options.fs;
|
||||
this.sandboxFactory = options.sandboxFactory;
|
||||
this.sandboxes = {};
|
||||
this.modules = options.modules;
|
||||
this.globals = options.globals;
|
||||
};
|
||||
|
||||
exports.Loader.prototype = {
|
||||
_makeRequire: function _makeRequire(rootDir) {
|
||||
var self = this;
|
||||
return function require(module) {
|
||||
if (module == "chrome") {
|
||||
var chrome = { Cc: Components.classes,
|
||||
Ci: Components.interfaces,
|
||||
Cu: Components.utils,
|
||||
Cr: Components.results,
|
||||
Cm: Components.manager,
|
||||
components: Components
|
||||
};
|
||||
return chrome;
|
||||
}
|
||||
var path = self.fs.resolveModule(rootDir, module);
|
||||
if (!path)
|
||||
throw new Error('Module "' + module + '" not found');
|
||||
if (!(path in self.modules)) {
|
||||
var options = self.fs.getFile(path);
|
||||
if (options.filename === undefined)
|
||||
options.filename = path;
|
||||
|
||||
var exports = {};
|
||||
var sandbox = self.sandboxFactory.createSandbox(options);
|
||||
self.sandboxes[path] = sandbox;
|
||||
for (name in self.globals)
|
||||
sandbox.defineProperty(name, self.globals[name]);
|
||||
sandbox.defineProperty('require', self._makeRequire(path));
|
||||
sandbox.evaluate("var exports = {};");
|
||||
let ES5 = self.modules.es5;
|
||||
if (ES5) {
|
||||
let { Object, Array, Function } = sandbox.globalScope;
|
||||
ES5.init(Object, Array, Function);
|
||||
}
|
||||
self.modules[path] = sandbox.getProperty("exports");
|
||||
sandbox.evaluate(options);
|
||||
}
|
||||
return self.modules[path];
|
||||
};
|
||||
},
|
||||
|
||||
// This is only really used by unit tests and other
|
||||
// development-related facilities, allowing access to symbols
|
||||
// defined in the global scope of a module.
|
||||
findSandboxForModule: function findSandboxForModule(module) {
|
||||
var path = this.fs.resolveModule(null, module);
|
||||
if (!path)
|
||||
throw new Error('Module "' + module + '" not found');
|
||||
if (!(path in this.sandboxes))
|
||||
this.require(module);
|
||||
if (!(path in this.sandboxes))
|
||||
throw new Error('Internal error: path not in sandboxes: ' +
|
||||
path);
|
||||
return this.sandboxes[path];
|
||||
},
|
||||
|
||||
require: function require(module) {
|
||||
return (this._makeRequire(null))(module);
|
||||
},
|
||||
|
||||
runScript: function runScript(options, extraOutput) {
|
||||
if (typeof(options) == 'string')
|
||||
options = {contents: options};
|
||||
options = {__proto__: options};
|
||||
var sandbox = this.sandboxFactory.createSandbox(options);
|
||||
if (extraOutput)
|
||||
extraOutput.sandbox = sandbox;
|
||||
for (name in this.globals)
|
||||
sandbox.defineProperty(name, this.globals[name]);
|
||||
sandbox.defineProperty('require', this._makeRequire(null));
|
||||
return sandbox.evaluate(options);
|
||||
}
|
||||
};
|
||||
|
||||
exports.CompositeFileSystem = function CompositeFileSystem(fses) {
|
||||
this.fses = fses;
|
||||
this._pathMap = {};
|
||||
};
|
||||
|
||||
exports.CompositeFileSystem.prototype = {
|
||||
resolveModule: function resolveModule(base, path) {
|
||||
for (var i = 0; i < this.fses.length; i++) {
|
||||
var fs = this.fses[i];
|
||||
var absPath = fs.resolveModule(base, path);
|
||||
if (absPath) {
|
||||
this._pathMap[absPath] = fs;
|
||||
return absPath;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
getFile: function getFile(path) {
|
||||
return this._pathMap[path].getFile(path);
|
||||
}
|
||||
};
|
||||
|
||||
exports.LocalFileSystem = function LocalFileSystem(root) {
|
||||
if (root === undefined) {
|
||||
if (!baseURI)
|
||||
throw new Error("Need a root path for module filesystem");
|
||||
root = baseURI;
|
||||
}
|
||||
if (typeof(root) == 'string')
|
||||
root = ios.newURI(root, null, baseURI);
|
||||
if (root instanceof Ci.nsIFile)
|
||||
root = ios.newFileURI(root);
|
||||
if (!(root instanceof Ci.nsIURI))
|
||||
throw new Error('Expected nsIFile, nsIURI, or string for root');
|
||||
|
||||
this.root = root.spec;
|
||||
this._rootURI = root;
|
||||
this._rootURIDir = getRootDir(root.spec);
|
||||
};
|
||||
|
||||
exports.LocalFileSystem.prototype = {
|
||||
resolveModule: function resolveModule(base, path) {
|
||||
path = path + ".js";
|
||||
|
||||
var baseURI;
|
||||
if (!base)
|
||||
baseURI = this._rootURI;
|
||||
else
|
||||
baseURI = ios.newURI(base);
|
||||
var newURI = ios.newURI(path, null, baseURI);
|
||||
var channel = NetUtil.newChannel({
|
||||
uri: newURI,
|
||||
loadUsingSystemPrincipal: true
|
||||
});
|
||||
try {
|
||||
channel.open2().close();
|
||||
} catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) {
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return newURI.spec;
|
||||
},
|
||||
getFile: function getFile(path) {
|
||||
var channel = NetUtil.newChannel({
|
||||
uri: path,
|
||||
loadUsingSystemPrincipal: true
|
||||
});
|
||||
var iStream = channel.open2();
|
||||
var ciStream = Cc["@mozilla.org/intl/converter-input-stream;1"].
|
||||
createInstance(Ci.nsIConverterInputStream);
|
||||
var bufLen = 0x8000;
|
||||
ciStream.init(iStream, "UTF-8", bufLen,
|
||||
Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
|
||||
var chunk = {};
|
||||
var data = "";
|
||||
while (ciStream.readString(bufLen, chunk) > 0)
|
||||
data += chunk.value;
|
||||
ciStream.close();
|
||||
iStream.close();
|
||||
return {contents: data};
|
||||
}
|
||||
};
|
||||
|
||||
if (global.window) {
|
||||
// We're being loaded in a chrome window, or a web page with
|
||||
// UniversalXPConnect privileges.
|
||||
global.SecurableModule = exports;
|
||||
} else if (global.exports) {
|
||||
// We're being loaded in a SecurableModule.
|
||||
for (name in exports) {
|
||||
global.exports[name] = exports[name];
|
||||
}
|
||||
} else {
|
||||
// We're being loaded in a JS module.
|
||||
global.EXPORTED_SYMBOLS = [];
|
||||
for (name in exports) {
|
||||
global.EXPORTED_SYMBOLS.push(name);
|
||||
global[name] = exports[name];
|
||||
}
|
||||
}
|
||||
})(this);
|
|
@ -1,17 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['trim', 'vslice'];
|
||||
|
||||
var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
|
||||
|
||||
var trim = function (str) {
|
||||
return (str.replace(/^[\s\xA0]+/, "").replace(/[\s\xA0]+$/, ""));
|
||||
}
|
||||
|
||||
var vslice = function (str, svalue, evalue) {
|
||||
var sindex = arrays.indexOf(str, svalue);
|
||||
var eindex = arrays.rindexOf(str, evalue);
|
||||
return str.slice(sindex + 1, eindex);
|
||||
}
|
|
@ -1,146 +0,0 @@
|
|||
/*
|
||||
Copyright (c) 2006 Lawrence Oluyede <l.oluyede@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
startsWith(str, prefix[, start[, end]]) -> bool
|
||||
|
||||
Return true if str ends with the specified prefix, false otherwise.
|
||||
With optional start, test str beginning at that position.
|
||||
With optional end, stop comparing str at that position.
|
||||
prefix can also be an array of strings to try.
|
||||
*/
|
||||
|
||||
var EXPORTED_SYMBOLS = ['startsWith', 'endsWith'];
|
||||
|
||||
function startsWith(str, prefix, start, end) {
|
||||
if (arguments.length < 2) {
|
||||
throw new TypeError('startsWith() requires at least 2 arguments');
|
||||
}
|
||||
|
||||
// check if start and end are null/undefined or a 'number'
|
||||
if ((start == null) || (isNaN(new Number(start)))) {
|
||||
start = 0;
|
||||
}
|
||||
if ((end == null) || (isNaN(new Number(end)))) {
|
||||
end = Number.MAX_VALUE;
|
||||
}
|
||||
|
||||
// if it's an array
|
||||
if (typeof prefix == "object") {
|
||||
for (var i = 0, j = prefix.length; i < j; i++) {
|
||||
var res = _stringTailMatch(str, prefix[i], start, end, true);
|
||||
if (res) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return _stringTailMatch(str, prefix, start, end, true);
|
||||
}
|
||||
|
||||
/*
|
||||
endsWith(str, suffix[, start[, end]]) -> bool
|
||||
|
||||
Return true if str ends with the specified suffix, false otherwise.
|
||||
With optional start, test str beginning at that position.
|
||||
With optional end, stop comparing str at that position.
|
||||
suffix can also be an array of strings to try.
|
||||
*/
|
||||
function endsWith(str, suffix, start, end) {
|
||||
if (arguments.length < 2) {
|
||||
throw new TypeError('endsWith() requires at least 2 arguments');
|
||||
}
|
||||
|
||||
// check if start and end are null/undefined or a 'number'
|
||||
if ((start == null) || (isNaN(new Number(start)))) {
|
||||
start = 0;
|
||||
}
|
||||
if ((end == null) || (isNaN(new Number(end)))) {
|
||||
end = Number.MAX_VALUE;
|
||||
}
|
||||
|
||||
// if it's an array
|
||||
if (typeof suffix == "object") {
|
||||
for (var i = 0, j = suffix.length; i < j; i++) {
|
||||
var res = _stringTailMatch(str, suffix[i], start, end, false);
|
||||
if (res) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return _stringTailMatch(str, suffix, start, end, false);
|
||||
}
|
||||
|
||||
/*
|
||||
Matches the end (direction == false) or start (direction == true) of str
|
||||
against substr, using the start and end arguments. Returns false
|
||||
if not found and true if found.
|
||||
*/
|
||||
function _stringTailMatch(str, substr, start, end, fromStart) {
|
||||
var len = str.length;
|
||||
var slen = substr.length;
|
||||
|
||||
var indices = _adjustIndices(start, end, len);
|
||||
start = indices[0]; end = indices[1]; len = indices[2];
|
||||
|
||||
if (fromStart) {
|
||||
if (start + slen > len) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (end - start < slen || start > len) {
|
||||
return false;
|
||||
}
|
||||
if (end - slen > start) {
|
||||
start = end - slen;
|
||||
}
|
||||
}
|
||||
|
||||
if (end - start >= slen) {
|
||||
return str.substr(start, slen) == substr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function _adjustIndices(start, end, len)
|
||||
{
|
||||
if (end > len) {
|
||||
end = len;
|
||||
} else if (end < 0) {
|
||||
end += len;
|
||||
}
|
||||
|
||||
if (end < 0) {
|
||||
end = 0;
|
||||
}
|
||||
if (start < 0) {
|
||||
start += len;
|
||||
}
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
}
|
||||
|
||||
return [start, end, len];
|
||||
}
|
|
@ -51,9 +51,6 @@ var hh = Cc["@mozilla.org/network/protocol;1?name=http"]
|
|||
var prefs = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefBranch);
|
||||
|
||||
var mozmillInit = {};
|
||||
Cu.import("resource://mozmill/driver/mozmill.js", mozmillInit);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "fileProtocolHandler", () => {
|
||||
let fileHandler = Services.io.getProtocolHandler("file");
|
||||
return fileHandler.QueryInterface(Ci.nsIFileProtocolHandler);
|
||||
|
@ -581,23 +578,6 @@ var TPS = {
|
|||
}
|
||||
},
|
||||
|
||||
MozmillEndTestListener: function TPS__MozmillEndTestListener(obj) {
|
||||
Logger.logInfo("mozmill endTest: " + JSON.stringify(obj));
|
||||
if (obj.failed > 0) {
|
||||
this.DumpError("mozmill test failed, name: " + obj.name + ", reason: " + JSON.stringify(obj.fails));
|
||||
} else if ("skipped" in obj && obj.skipped) {
|
||||
this.DumpError("mozmill test failed, name: " + obj.name + ", reason: " + obj.skipped_reason);
|
||||
} else {
|
||||
CommonUtils.namedTimer(function() {
|
||||
this.FinishAsyncOperation();
|
||||
}, 2000, this, "postmozmilltest");
|
||||
}
|
||||
},
|
||||
|
||||
MozmillSetTestListener: function TPS__MozmillSetTestListener(obj) {
|
||||
Logger.logInfo("mozmill setTest: " + obj.name);
|
||||
},
|
||||
|
||||
async Cleanup() {
|
||||
try {
|
||||
await this.WipeServer();
|
||||
|
@ -1045,25 +1025,6 @@ var TPS = {
|
|||
this._enabledEngines = names;
|
||||
},
|
||||
|
||||
RunMozmillTest: function TPS__RunMozmillTest(testfile) {
|
||||
var mozmillfile = Cc["@mozilla.org/file/local;1"]
|
||||
.createInstance(Ci.nsIFile);
|
||||
if (hh.oscpu.toLowerCase().indexOf("windows") > -1) {
|
||||
let re = /\/(\w)\/(.*)/;
|
||||
this.config.testdir = this.config.testdir.replace(re, "$1://$2").replace(/\//g, "\\");
|
||||
}
|
||||
mozmillfile.initWithPath(this.config.testdir);
|
||||
mozmillfile.appendRelativePath(testfile);
|
||||
Logger.logInfo("Running mozmill test " + mozmillfile.path);
|
||||
|
||||
var frame = {};
|
||||
Cu.import("resource://mozmill/modules/frame.js", frame);
|
||||
frame.events.addListener("setTest", this.MozmillSetTestListener.bind(this));
|
||||
frame.events.addListener("endTest", this.MozmillEndTestListener.bind(this));
|
||||
this.StartAsyncOperation();
|
||||
frame.runTestFile(mozmillfile.path, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves when a specific observer notification is
|
||||
* resolved. This is similar to the various waitFor* functions, although is
|
||||
|
|
|
@ -631,7 +631,7 @@ version = "0.5.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"metadeps 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"metadeps 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -854,11 +854,6 @@ dependencies = [
|
|||
"regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error-chain"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "error-chain"
|
||||
version = "0.10.0"
|
||||
|
@ -1713,10 +1708,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "metadeps"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"error-chain 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@ -3590,7 +3585,7 @@ version = "2.14.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"metadeps 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"metadeps 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3729,7 +3724,6 @@ dependencies = [
|
|||
"checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180"
|
||||
"checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b"
|
||||
"checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8"
|
||||
"checksum error-chain 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "318cb3c71ee4cdea69fdc9e15c173b245ed6063e1709029e8fd32525a881120f"
|
||||
"checksum euclid 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)" = "01a550d73de4eac12cee6316ccef84a9f71493c3839015bfda6465a53e31b879"
|
||||
"checksum expat-sys 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c470ccb972f2088549b023db8029ed9da9426f5affbf9b62efff7009ab8ed5b1"
|
||||
"checksum extra-default 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca1434e1920ba21b45516c16b5edbd82e8f2e4349b12a7a53eb9903ed2928d56"
|
||||
|
@ -3793,7 +3787,7 @@ dependencies = [
|
|||
"checksum markup5ever 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2cf89d3e0486c32c9d99521455ddf9a438910a1ce2bd376936086edc15dff5fc"
|
||||
"checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1"
|
||||
"checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4"
|
||||
"checksum metadeps 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829fffe7ea1d747e23f64be972991bc516b2f1ac2ae4a3b33d8bea150c410151"
|
||||
"checksum metadeps 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "73b122901b3a675fac8cecf68dcb2f0d3036193bc861d1ac0e1c337f7d5254c2"
|
||||
"checksum mime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9d69889cdc6336ed56b174514ce876c4c3dc564cc23dd872e7bca589bb2a36c8"
|
||||
"checksum mime_guess 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76da6df85047af8c0edfa53f48eb1073012ce1cc95c8fedc0a374f659a89dd65"
|
||||
"checksum miniz-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "28eaee17666671fa872e567547e8428e83308ebe5808cdf6a0e28397dbe2c726"
|
||||
|
|
|
@ -10,7 +10,6 @@ use platform::font::{FontHandle, FontTable};
|
|||
use platform::font_context::FontContextHandle;
|
||||
use platform::font_template::FontTemplateData;
|
||||
use smallvec::SmallVec;
|
||||
use std::ascii::AsciiExt;
|
||||
use std::borrow::ToOwned;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
use hyper::method::Method;
|
||||
use net_traits::request::{CredentialsMode, Origin, Request};
|
||||
use servo_url::ServoUrl;
|
||||
use std::ascii::AsciiExt;
|
||||
use time::{self, Timespec};
|
||||
|
||||
/// Union type for CORS cache entries
|
||||
|
|
|
@ -21,7 +21,6 @@ use net_traits::request::{CredentialsMode, Destination, Referrer, Request, Reque
|
|||
use net_traits::request::{ResponseTainting, Origin, Window};
|
||||
use net_traits::response::{Response, ResponseBody, ResponseType};
|
||||
use servo_url::ServoUrl;
|
||||
use std::ascii::AsciiExt;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
|
|
|
@ -39,7 +39,6 @@ use net_traits::request::{ResponseTainting, ServiceWorkersMode};
|
|||
use net_traits::response::{HttpsState, Response, ResponseBody, ResponseType};
|
||||
use resource_thread::AuthCache;
|
||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||
use std::ascii::AsciiExt;
|
||||
use std::collections::HashSet;
|
||||
use std::error::Error;
|
||||
use std::io::{self, Read, Write};
|
||||
|
|
|
@ -20,7 +20,6 @@ use net_traits::{CookieSource, MessageData, NetworkError};
|
|||
use net_traits::{WebSocketDomAction, WebSocketNetworkEvent};
|
||||
use net_traits::request::{Destination, RequestInit, RequestMode};
|
||||
use servo_url::ServoUrl;
|
||||
use std::ascii::AsciiExt;
|
||||
use std::io::{self, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
|
|
@ -9,7 +9,6 @@ use hyper::header::{AccessControlExposeHeaders, ContentType, Headers};
|
|||
use hyper::status::StatusCode;
|
||||
use hyper_serde::Serde;
|
||||
use servo_url::ServoUrl;
|
||||
use std::ascii::AsciiExt;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// [Response type](https://fetch.spec.whatwg.org/#concept-response-type)
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
use cssparser::CowRcStr;
|
||||
use html5ever::{LocalName, Namespace};
|
||||
use servo_atoms::Atom;
|
||||
use std::ascii::AsciiExt;
|
||||
use std::borrow::{Borrow, Cow, ToOwned};
|
||||
use std::default::Default;
|
||||
use std::fmt;
|
||||
|
|
|
@ -16,7 +16,6 @@ use ipc_channel::ipc;
|
|||
use net_traits::{CoreResourceMsg, IpcSend};
|
||||
use net_traits::blob_url_store::{BlobBuf, get_blob_origin};
|
||||
use net_traits::filemanager_thread::{FileManagerThreadMsg, ReadFileProgress, RelativePos};
|
||||
use std::ascii::AsciiExt;
|
||||
use std::mem;
|
||||
use std::ops::Index;
|
||||
use std::path::PathBuf;
|
||||
|
|
|
@ -16,7 +16,6 @@ use dom::window::Window;
|
|||
use dom_struct::dom_struct;
|
||||
use servo_arc::Arc;
|
||||
use servo_url::ServoUrl;
|
||||
use std::ascii::AsciiExt;
|
||||
use style::attr::AttrValue;
|
||||
use style::properties::{DeclarationSource, Importance, PropertyDeclarationBlock, PropertyId, LonghandId, ShorthandId};
|
||||
use style::properties::{parse_one_declaration_into, parse_style_attribute, SourcePropertyDeclaration};
|
||||
|
|
|
@ -117,7 +117,6 @@ use servo_arc::Arc;
|
|||
use servo_atoms::Atom;
|
||||
use servo_config::prefs::PREFS;
|
||||
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
||||
use std::ascii::AsciiExt;
|
||||
use std::borrow::ToOwned;
|
||||
use std::cell::{Cell, Ref, RefMut};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
|
|
|
@ -93,7 +93,6 @@ use selectors::matching::{ElementSelectorFlags, MatchingContext, RelevantLinkSta
|
|||
use selectors::sink::Push;
|
||||
use servo_arc::Arc;
|
||||
use servo_atoms::Atom;
|
||||
use std::ascii::AsciiExt;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::{Cell, Ref};
|
||||
use std::default::Default;
|
||||
|
|
|
@ -30,7 +30,6 @@ use dom::nodelist::NodeList;
|
|||
use dom::virtualmethods::VirtualMethods;
|
||||
use dom_struct::dom_struct;
|
||||
use html5ever::{LocalName, Prefix};
|
||||
use std::ascii::AsciiExt;
|
||||
use std::collections::HashSet;
|
||||
use std::default::Default;
|
||||
use std::rc::Rc;
|
||||
|
|
|
@ -26,7 +26,6 @@ use html5ever::{LocalName, Prefix};
|
|||
use net_traits::ReferrerPolicy;
|
||||
use script_traits::{MozBrowserEvent, ScriptMsg};
|
||||
use servo_arc::Arc;
|
||||
use std::ascii::AsciiExt;
|
||||
use std::borrow::ToOwned;
|
||||
use std::cell::Cell;
|
||||
use std::default::Default;
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче