Merge mozilla-central to b2g-inbound

This commit is contained in:
Carsten "Tomcat" Book 2015-02-26 12:09:46 +01:00
Родитель edde057077 c658a426c3
Коммит 6c6b839f32
216 изменённых файлов: 9841 добавлений и 1905 удалений

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

@ -223,7 +223,7 @@ let OutputGenerator = {
},
_getOutputName: function _getOutputName(aName) {
return aName.replace(' ', '');
return aName.replace(/\s/g, '');
},
roleRuleMap: {

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

@ -848,7 +848,8 @@ PivotContext.prototype = {
hints.push(hint);
} else if (aAccessible.actionCount > 0) {
hints.push({
string: Utils.AccRetrieval.getStringRole(aAccessible.role) + '-hint'
string: Utils.AccRetrieval.getStringRole(
aAccessible.role).replace(/\s/g, '') + '-hint'
});
}
});

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

@ -43,6 +43,9 @@
accOrElmOrID: 'nested_link3',
expectedHints: [{string: 'link-hint'}, {string: 'pushbutton-hint'},
"Double tap and hold to activate"]
}, {
accOrElmOrID: 'menuitemradio',
expectedHints: [{string: 'radiomenuitem-hint'}]
}];
// Test hints.
@ -80,6 +83,7 @@
<a href="#" id="link_with_hint_override" aria-moz-hint="Tap and hold to get to menu">I am a special link</a>
<button id="button_with_default_hint">Toggle</button>
<button id="button_with_hint_override" aria-moz-hint="Tap and hold to activate">Special</button>
<span id="menuitemradio" role="menuitemradio">Item 1</span>
</div>
</body>
</html>

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

@ -1,6 +1,7 @@
sudo: false
language: node_js
node_js:
- "iojs"
- "0.10"
notifications:

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

@ -17,12 +17,14 @@ If you have code that you'd like to contribute the Jetpack project, follow these
3. Make your changes, per the Overview
4. Write a test ([intro][test intro], [API][test API])
5. Submit pull request with changes and a title in a form of `Bug XXX - description`
6. Copy the pull request link from GitHub and paste it in as an attachment to the bug
7. Each pull request should idealy contain only one commit, so squash the commits if necessary.
8. Flag the attachment for code review from one of the Jetpack reviewers listed below.
6. Make sure that [Travis CI](https://travis-ci.org/mozilla/addon-sdk/branches) tests are passing for your branch.
7. Copy the pull request link from GitHub and paste it in as an attachment to the bug
8. Each pull request should idealy contain only one commit, so squash the commits if necessary.
9. Flag the attachment for code review from one of the Jetpack reviewers listed below.
This step is optional, but could speed things up.
9. Address any nits (ie style changes), or other issues mentioned in the review.
10. Finally, once review is approved, a team member will do the merging
10. Address any nits (ie style changes), or other issues mentioned in the review.
Finally, once review is approved, a team member will do the merging
## Good First Bugs

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

@ -81,14 +81,12 @@ function startup(reason, options) Startup.onceInitialized.then(() => {
// Exports data to a pseudo module so that api-utils/l10n/core
// can get access to it
definePseudo(options.loader, '@l10n/data', data ? data : null);
return ready;
}).then(function() {
run(options);
return ready.then(() => run(options, !!data));
}).then(null, console.exception);
return void 0; // otherwise we raise a warning, see bug 910304
});
function run(options) {
function run(options, hasL10n) {
try {
// Try initializing HTML localization before running main module. Just print
// an exception in case of error, instead of preventing addon to be run.
@ -96,7 +94,7 @@ function run(options) {
// Do not enable HTML localization while running test as it is hard to
// disable. Because unit tests are evaluated in a another Loader who
// doesn't have access to this current loader.
if (options.main !== 'sdk/test/runner') {
if (hasL10n && options.main !== 'sdk/test/runner') {
require('../l10n/html').enable();
}
}

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

@ -340,7 +340,7 @@ function getPotentialLeaks() {
let item = {
path: matches[1],
principal: details[1],
location: details[2] ? details[2].replace("\\", "/", "g") : undefined,
location: details[2] ? details[2].replace(/\\/g, "/") : undefined,
source: details[3] ? details[3].split(" -> ").reverse() : undefined,
toString: function() this.location
};
@ -364,8 +364,8 @@ function getPotentialLeaks() {
let item = {
path: matches[1],
location: details[1].replace("\\", "/", "g"),
source: [details[1].replace("\\", "/", "g")],
location: details[1].replace(/\\/g, "/"),
source: [details[1].replace(/\\/g, "/")],
toString: function() this.location
};

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

@ -12,6 +12,7 @@ const { emit } = require("../event/core");
const { WindowTracker, windowIterator } = require("../deprecated/window-utils");
const { DOMEventAssembler } = require("../deprecated/events/assembler");
const { Class } = require("../core/heritage");
const { Cu } = require("chrome");
// Event emitter objects used to register listeners and emit events on them
// when they occur.
@ -42,6 +43,9 @@ const Observer = Class({
* Keyboard event being emitted.
*/
handleEvent(event) {
// Ignore events from windows in the child process as they can't be top-level
if (Cu.isCrossProcessWrapper(event.target))
return;
emit(this, event.type, event.target, event);
}
});

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

@ -703,7 +703,8 @@ Loader.main = main;
const Module = iced(function Module(id, uri) {
return create(null, {
id: { enumerable: true, value: id },
exports: { enumerable: true, writable: true, value: create(null) },
exports: { enumerable: true, writable: true, value: create(null),
configurable: true },
uri: { value: uri }
});
});
@ -784,6 +785,7 @@ function Loader(options) {
}
}, modules);
const builtinModuleExports = modules;
modules = keys(modules).reduce(function(result, id) {
// We resolve `uri` from `id` since modules are cached by `uri`.
let uri = resolveURI(id, mapping);
@ -792,7 +794,16 @@ function Loader(options) {
if (isNative && !uri)
uri = id;
let module = Module(id, uri);
module.exports = freeze(modules[id]);
// Lazily expose built-in modules in order to
// allow them to be loaded lazily.
Object.defineProperty(module, "exports", {
enumerable: true,
get: function() {
return builtinModuleExports[id];
}
});
result[uri] = freeze(module);
return result;
}, {});

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

@ -60,6 +60,7 @@ DEFAULT_NO_CONNECTIONS_PREFS = {
'browser.safebrowsing.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
'browser.safebrowsing.reportURL': 'http://localhost/safebrowsing-dummy/report',
'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport',
'browser.selfsupport.url': 'http://localhost/repair-dummy',
'browser.trackingprotection.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
'browser.trackingprotection.updateURL': 'http://localhost/safebrowsing-dummy/update',

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

@ -10,7 +10,8 @@ const { fromIterator } = require('sdk/util/array');
const BUILTIN_SIDEBAR_MENUITEMS = exports.BUILTIN_SIDEBAR_MENUITEMS = [
'menu_socialSidebar',
'menu_historySidebar',
'menu_bookmarksSidebar'
'menu_bookmarksSidebar',
'menu_readingListSidebar'
];
function isSidebarShowing(window) {

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

@ -129,8 +129,7 @@ exports.testDestroyEdgeCaseBugWithPrivateWindow = function(assert, done) {
assert.pass('onShow works for Sidebar');
loader.unload();
let sidebarMI = getSidebarMenuitems();
for (let mi of sidebarMI) {
for (let mi of getSidebarMenuitems()) {
assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar')
assert.ok(!isChecked(mi), 'no sidebar menuitem is checked');
}

9
addon-sdk/source/test/fixtures/loader/lazy/main.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
exports.useFoo= function () {
return require('foo');
}

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

@ -17,6 +17,7 @@
"browser.safebrowsing.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
"browser.safebrowsing.reportURL": "http://localhost/safebrowsing-dummy/report",
"browser.safebrowsing.malware.reportURL": "http://localhost/safebrowsing-dummy/malwarereport",
"browser.selfsupport.url": "http://localhost/repair-dummy",
"browser.trackingprotection.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
"browser.trackingprotection.updateURL": "http://localhost/safebrowsing-dummy/update",
"browser.newtabpage.directory.source": "data:application/json,{'jetpack':1}",

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

@ -16,7 +16,8 @@ const { fromIterator } = require('sdk/util/array');
const BUILTIN_SIDEBAR_MENUITEMS = exports.BUILTIN_SIDEBAR_MENUITEMS = [
'menu_socialSidebar',
'menu_historySidebar',
'menu_bookmarksSidebar'
'menu_bookmarksSidebar',
'menu_readingListSidebar'
];
function isSidebarShowing(window) {

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

@ -506,4 +506,22 @@ exports["test Cu.import in b2g style"] = (assert) => {
"Loader.Loader is a funciton");
};
exports['test lazy globals'] = function (assert) {
let uri = root + '/fixtures/loader/lazy/';
let gotFoo = false;
let foo = {};
let modules = {
get foo() {
gotFoo = true;
return foo;
}
};
let loader = Loader({ paths: { '': uri }, modules: modules});
assert.ok(!gotFoo, "foo hasn't been accessed during loader instanciation");
let program = main(loader, 'main');
assert.ok(!gotFoo, "foo hasn't been accessed during module loading");
assert.equal(program.useFoo(), foo, "foo mock works");
assert.ok(gotFoo, "foo has been accessed only when we first try to use it");
};
require('sdk/test').run(exports);

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

@ -29,6 +29,17 @@ exports.testGetAndSet = function(assert) {
"preferences-service should read from " +
"application-wide preferences service");
// test getting a pref that does not exist,
// and where we provide no default
assert.equal(
prefs.get("test_dne_get_pref", "default"),
"default",
"default was used for a pref that does not exist");
assert.equal(
prefs.get("test_dne_get_pref"),
undefined,
"undefined was returned for a pref that does not exist with no default");
prefs.set("test_set_get_pref.integer", 1);
assert.equal(prefs.get("test_set_get_pref.integer"), 1,
"set/get integer preference should work");

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

@ -96,9 +96,12 @@ exports.testSidebarBasicLifeCycle = function*(assert) {
sidebar.destroy();
sidebar.destroy();
let sidebarMI = getSidebarMenuitems();
for (let mi of sidebarMI) {
assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar')
for (let mi of getSidebarMenuitems()) {
let id = mi.getAttribute('id');
if (BUILTIN_SIDEBAR_MENUITEMS.indexOf(id) < 0) {
assert.fail('the menuitem "' + id + '" is not a built-in sidebar');
}
assert.ok(!isChecked(mi), 'no sidebar menuitem is checked');
}
@ -360,8 +363,7 @@ exports.testSidebarUnload = function*(assert) {
loader.unload();
let sidebarMI = getSidebarMenuitems();
for (let mi of sidebarMI) {
for (let mi of getSidebarMenuitems()) {
assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar')
assert.ok(!isChecked(mi), 'no sidebar menuitem is checked');
}
@ -598,9 +600,12 @@ exports.testDestroyEdgeCaseBug = function*(assert) {
yield sidebar.show();
loader.unload();
let sidebarMI = getSidebarMenuitems();
for (let mi of sidebarMI) {
assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar')
for (let mi of getSidebarMenuitems()) {
let id = mi.getAttribute('id');
if (BUILTIN_SIDEBAR_MENUITEMS.indexOf(id) < 0) {
assert.fail('the menuitem "' + id + '" is not a built-in sidebar');
}
assert.ok(!isChecked(mi), 'no sidebar menuitem is checked');
}
assert.ok(!window.document.getElementById(makeID(testName)), 'sidebar id DNE');
@ -762,6 +767,9 @@ exports.testURLSetterToSameValueReloadsSidebar = function*(assert) {
document = window.document;
assert.pass('new window was opened');
yield focus(window);
assert.pass('new window was focused');
yield sidebar1.show();
assert.equal(isShowing(sidebar1), true, 'the sidebar is showing');
@ -1490,23 +1498,29 @@ exports.testShowHideRawWindowArg = function*(assert) {
});
let mainWindow = getMostRecentBrowserWindow();
let newWindow = yield open().then(focus);
let newWindow = yield windowPromise(mainWindow.OpenBrowserWindow(), 'load');
assert.pass("Created the new window");
yield focus(newWindow);
assert.pass("Focused the new window");
yield focus(mainWindow);
assert.pass("Focused the old window");
yield sidebar.show(newWindow);
assert.pass('the sidebar was shown');
assert.ok(!isSidebarShowing(mainWindow), 'sidebar is not showing in main window');
assert.ok(isSidebarShowing(newWindow), 'sidebar is showing in new window');
assert.equal(isSidebarShowing(mainWindow), false, 'sidebar is not showing in main window');
assert.equal(isSidebarShowing(newWindow), true, 'sidebar is showing in new window');
assert.ok(isFocused(mainWindow), 'main window is still focused');
yield sidebar.hide(newWindow);
assert.ok(isFocused(mainWindow), 'main window is still focused');
assert.ok(!isSidebarShowing(mainWindow), 'sidebar is not showing in main window');
assert.ok(!isSidebarShowing(newWindow), 'sidebar is not showing in new window');
assert.equal(isFocused(mainWindow), true, 'main window is still focused');
assert.equal(isSidebarShowing(mainWindow), false, 'sidebar is not showing in main window');
assert.equal(isSidebarShowing(newWindow), false, 'sidebar is not showing in new window');
sidebar.destroy();
}
@ -1547,6 +1561,7 @@ before(exports, (name, assert) => {
});
after(exports, function*(name, assert) {
assert.pass("Cleaning new windows and tabs");
yield cleanUI();
assert.equal(isSidebarShowing(), false, 'no sidebar is showing');
});

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

@ -5,10 +5,7 @@
const { Cu } = require('chrome');
const { Loader } = require('sdk/test/loader');
function gc() {
return new Promise(resolve => Cu.schedulePreciseGC(resolve));
};
const { gc } = require("sdk/test/memory");
exports['test adding item'] = function*(assert) {
let loader = Loader(module);
@ -121,31 +118,31 @@ exports['test adding non object or null item'] = function(assert) {
assert.throws(() => {
add(items, 'foo');
},
/^value is not a non-null object/,
/^\w+ is not a non-null object/,
'only non-null object are allowed');
assert.throws(() => {
add(items, 0);
},
/^value is not a non-null object/,
/^\w+ is not a non-null object/,
'only non-null object are allowed');
assert.throws(() => {
add(items, undefined);
},
/^value is not a non-null object/,
/^\w+ is not a non-null object/,
'only non-null object are allowed');
assert.throws(() => {
add(items, null);
},
/^value is not a non-null object/,
/^\w+ is not a non-null object/,
'only non-null object are allowed');
assert.throws(() => {
add(items, true);
},
/^value is not a non-null object/,
/^\w+ is not a non-null object/,
'only non-null object are allowed');
};
@ -158,31 +155,31 @@ exports['test adding to non object or null item'] = function(assert) {
assert.throws(() => {
add('foo', item);
},
/^value is not a non-null object/,
/^\w+ is not a non-null object/,
'only non-null object are allowed');
assert.throws(() => {
add(0, item);
},
/^value is not a non-null object/,
/^\w+ is not a non-null object/,
'only non-null object are allowed');
assert.throws(() => {
add(undefined, item);
},
/^value is not a non-null object/,
/^\w+ is not a non-null object/,
'only non-null object are allowed');
assert.throws(() => {
add(null, item);
},
/^value is not a non-null object/,
/^\w+ is not a non-null object/,
'only non-null object are allowed');
assert.throws(() => {
add(true, item);
},
/^value is not a non-null object/,
/^\w+ is not a non-null object/,
'only non-null object are allowed');
};

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

@ -1336,7 +1336,7 @@ pref("browser.devedition.theme.showCustomizeButton", false);
// Developer edition promo preferences
pref("devtools.devedition.promo.shown", false);
pref("devtools.devedition.promo.url", "https://mozilla.org/firefox/developer");
pref("devtools.devedition.promo.url", "https://www.mozilla.org/firefox/developer/?utm_source=firefox-dev-tools&utm_medium=firefox-browser&utm_content=betadoorhanger");
// Only potentially show in beta release
#if MOZ_UPDATE_CHANNEL == beta
@ -1438,6 +1438,9 @@ pref("devtools.timeline.enabled", true);
#else
pref("devtools.timeline.enabled", false);
#endif
// TODO remove `devtools.timeline.hiddenMarkers.` branches when performance
// tool lands (bug 1075567)
pref("devtools.timeline.hiddenMarkers", "[]");
// Enable perftools via build command
@ -1455,6 +1458,7 @@ pref("devtools.profiler.ui.show-platform-data", false);
pref("devtools.profiler.ui.show-idle-blocks", true);
// The default Performance UI settings
pref("devtools.performance.timeline.hidden-markers", "[]");
pref("devtools.performance.ui.invert-call-tree", true);
pref("devtools.performance.ui.invert-flame-graph", false);
pref("devtools.performance.ui.flatten-tree-recursion", true);

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

@ -688,7 +688,6 @@ let SessionStoreInternal = {
Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED, null);
}
delete browser.__SS_restore_data;
delete browser.__SS_data;
SessionStoreInternal._resetLocalTabRestoringState(tab);
@ -2683,26 +2682,16 @@ let SessionStoreInternal = {
},
/**
* Restores the specified tab. If the tab can't be restored (eg, no history or
* calling gotoIndex fails), then state changes will be rolled back.
* This method will check if gTabsProgressListener is attached to the tab's
* window, ensuring that we don't get caught without one.
* This method removes the session history listener right before starting to
* attempt a load. This will prevent cases of "stuck" listeners.
* If this method returns false, then it is up to the caller to decide what to
* do. In the common case (restoreNextTab), we will want to then attempt to
* restore the next tab. In the other case (selecting the tab, reloading the
* tab), the caller doesn't actually want to do anything if no page is loaded.
* Kicks off restoring the given tab.
*
* @param aTab
* the tab to restore
*
* @returns true/false indicating whether or not a load actually happened
* @param aLoadArguments
* optional load arguments used for loadURI()
*/
restoreTabContent: function (aTab, aLoadArguments = null) {
let window = aTab.ownerDocument.defaultView;
let browser = aTab.linkedBrowser;
let tabData = browser.__SS_data;
// Make sure that this tab is removed from the priority queue.
TabRestoreQueue.remove(aTab);
@ -2715,17 +2704,6 @@ let SessionStoreInternal = {
browser.removeAttribute("pending");
aTab.removeAttribute("pending");
let activeIndex = tabData.index - 1;
// Attach data that will be restored on "load" event, after tab is restored.
if (tabData.entries.length) {
// restore those aspects of the currently active documents which are not
// preserved in the plain history entries (mainly scroll state and text data)
browser.__SS_restore_data = tabData.entries[activeIndex] || {};
} else {
browser.__SS_restore_data = {};
}
browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent",
{loadArguments: aLoadArguments});
},

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

@ -162,7 +162,9 @@ let SnapshotsListView = Heritage.extend(WidgetMethods, {
* The select listener for this container.
*/
_onSelect: function({ detail: snapshotItem }) {
if (!snapshotItem) {
// Check to ensure the attachment has an actor, like
// an in-progress recording.
if (!snapshotItem || !snapshotItem.attachment.actor) {
return;
}
let { calls, thumbnails, screenshot } = snapshotItem.attachment;

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

@ -4,6 +4,7 @@ support-files =
doc_raf-begin.html
doc_settimeout.html
doc_no-canvas.html
doc_raf-no-canvas.html
doc_simple-canvas.html
doc_simple-canvas-bitmasks.html
doc_simple-canvas-deep-stack.html
@ -44,7 +45,9 @@ skip-if = e10s # bug 1102301 - leaks while running as a standalone directory in
[browser_canvas-frontend-reload-02.js]
[browser_canvas-frontend-slider-01.js]
[browser_canvas-frontend-slider-02.js]
[browser_canvas-frontend-snapshot-select.js]
[browser_canvas-frontend-snapshot-select-01.js]
[browser_canvas-frontend-snapshot-select-02.js]
[browser_canvas-frontend-stepping.js]
[browser_canvas-frontend-stop-01.js]
[browser_canvas-frontend-stop-02.js]
[browser_canvas-frontend-stop-03.js]

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

@ -0,0 +1,30 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if selecting snapshots in the frontend displays the appropriate data
* respective to their recorded animation frame.
*/
function ifTestingSupported() {
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
yield reload(target);
SnapshotsListView._onRecordButtonClick();
let snapshotTarget = SnapshotsListView.getItemAtIndex(0).target;
EventUtils.sendMouseEvent({ type: "mousedown" }, snapshotTarget, window);
EventUtils.sendMouseEvent({ type: "mousedown" }, snapshotTarget, window);
EventUtils.sendMouseEvent({ type: "mousedown" }, snapshotTarget, window);
ok(true, "clicking in-progress snapshot does not fail");
let finished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
SnapshotsListView._onRecordButtonClick();
yield finished;
yield teardown(panel);
finish();
}

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

@ -0,0 +1,36 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that a recording that has a rAF cycle, but no draw calls, fails
* after timeout.
*/
function ifTestingSupported() {
let { target, panel } = yield initCanvasDebuggerFrontend(RAF_NO_CANVAS_URL);
let { window, EVENTS, $, SnapshotsListView } = panel.panelWin;
yield reload(target);
let recordingStarted = once(window, EVENTS.SNAPSHOT_RECORDING_STARTED);
SnapshotsListView._onRecordButtonClick();
yield recordingStarted;
is($("#empty-notice").hidden, true, "Empty notice not shown");
is($("#waiting-notice").hidden, false, "Waiting notice shown");
let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
let recordingCancelled = once(window, EVENTS.SNAPSHOT_RECORDING_CANCELLED);
yield promise.all([recordingFinished, recordingCancelled]);
ok(true, "Recording stopped and was considered failed.");
is(SnapshotsListView.itemCount, 0, "No snapshots in the list.");
is($("#empty-notice").hidden, false, "Empty notice shown");
is($("#waiting-notice").hidden, true, "Waiting notice not shown");
yield teardown(panel);
finish();
}

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

@ -0,0 +1,18 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Canvas inspector test page</title>
</head>
<body>
<script>
function render () { window.requestAnimationFrame(render); }
window.requestAnimationFrame(render);
</script>
</body>
</html>

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

@ -30,6 +30,7 @@ const FRAME_SCRIPT_UTILS_URL = "chrome://browser/content/devtools/frame-script-u
const EXAMPLE_URL = "http://example.com/browser/browser/devtools/canvasdebugger/test/";
const SET_TIMEOUT_URL = EXAMPLE_URL + "doc_settimeout.html";
const NO_CANVAS_URL = EXAMPLE_URL + "doc_no-canvas.html";
const RAF_NO_CANVAS_URL = EXAMPLE_URL + "doc_raf-no-canvas.html";
const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html";
const SIMPLE_BITMASKS_URL = EXAMPLE_URL + "doc_simple-canvas-bitmasks.html";
const SIMPLE_CANVAS_TRANSPARENT_URL = EXAMPLE_URL + "doc_simple-canvas-transparent.html";

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

@ -14,6 +14,11 @@
min-width: 100%;
}
body.dragging .tag-line {
cursor: grabbing;
-moz-user-select: none;
}
#root-wrapper:after {
content: "";
display: block;
@ -63,6 +68,40 @@
padding-left: 1001em;
}
/* Normally this element takes space in the layout even if it's position: relative
* by adding height: 0 we let surrounding elements to fill the blank space */
.child.dragging {
position: relative;
pointer-events: none;
opacity: 0.7;
height: 0;
}
/* Indicates a tag-line in the markup-view as being an active drop target by
* drawing a horizontal line where the dragged element would be inserted if
* dropped here */
.tag-line.drop-target::before, .tag-line.drag-target::before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
}
.tag-line.drag-target::before {
border-top: 2px dashed var(--theme-contrast-background);
}
.tag-line.drop-target::before {
border-top: 2px dashed var(--theme-content-color1);
}
/* In case the indicator is put on the closing .tag-line, the indentation level
* will become misleading, so we push it forward to match the indentation level */
ul.children + .tag-line::before {
margin-left: 14px;
}
.tag-line {
min-height: 1.4em;
line-height: 1.4em;

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

@ -14,6 +14,10 @@ const COLLAPSE_ATTRIBUTE_LENGTH = 120;
const COLLAPSE_DATA_URL_REGEX = /^data.+base64/;
const COLLAPSE_DATA_URL_LENGTH = 60;
const NEW_SELECTION_HIGHLIGHTER_TIMER = 1000;
const GRAB_DELAY = 400;
const DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE = 50;
const DRAG_DROP_MIN_AUTOSCROLL_SPEED = 5;
const DRAG_DROP_MAX_AUTOSCROLL_SPEED = 15;
const {UndoStack} = require("devtools/shared/undo");
const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
@ -62,6 +66,7 @@ function MarkupView(aInspector, aFrame, aControllerWindow) {
this._inspector = aInspector;
this.walker = this._inspector.walker;
this._frame = aFrame;
this.win = this._frame.contentWindow;
this.doc = this._frame.contentDocument;
this._elt = this.doc.querySelector("#root");
this.htmlEditor = new HTMLEditor(this.doc);
@ -94,6 +99,9 @@ function MarkupView(aInspector, aFrame, aControllerWindow) {
this._onMouseClick = this._onMouseClick.bind(this);
this._onMouseUp = this._onMouseUp.bind(this);
this.doc.body.addEventListener("mouseup", this._onMouseUp);
this._boundOnNewSelection = this._onNewSelection.bind(this);
this._inspector.selection.on("new-node-front", this._boundOnNewSelection);
this._onNewSelection();
@ -156,7 +164,49 @@ MarkupView.prototype = {
}
},
isDragging: false,
_onMouseMove: function(event) {
if (this.isDragging) {
event.preventDefault();
this._dragStartEl = event.target;
let docEl = this.doc.documentElement;
if (this._scrollInterval) {
this.win.clearInterval(this._scrollInterval);
}
// Auto-scroll when the mouse approaches top/bottom edge
let distanceFromBottom = docEl.clientHeight - event.pageY + this.win.scrollY,
distanceFromTop = event.pageY - this.win.scrollY;
if (distanceFromBottom <= DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE) {
// Map our distance from 0-50 to 5-15 range so the speed is kept
// in a range not too fast, not too slow
let speed = map(distanceFromBottom, 0, DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE,
DRAG_DROP_MIN_AUTOSCROLL_SPEED, DRAG_DROP_MAX_AUTOSCROLL_SPEED);
// Here, we use minus because the value of speed - 15 is always negative
// and it makes the speed relative to the distance between mouse and edge
// the closer to the edge, the faster
this._scrollInterval = this.win.setInterval(() => {
docEl.scrollTop -= speed - DRAG_DROP_MAX_AUTOSCROLL_SPEED;
}, 0);
}
if (distanceFromTop <= DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE) {
// refer to bottom edge's comments for more info
let speed = map(distanceFromTop, 0, DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE,
DRAG_DROP_MIN_AUTOSCROLL_SPEED, DRAG_DROP_MAX_AUTOSCROLL_SPEED);
this._scrollInterval = this.win.setInterval(() => {
docEl.scrollTop += speed - DRAG_DROP_MAX_AUTOSCROLL_SPEED;
}, 0);
}
return;
};
let target = event.target;
// Search target for a markupContainer reference, if not found, walk up
@ -198,6 +248,18 @@ MarkupView.prototype = {
}
},
_onMouseUp: function() {
if (this._lastDropTarget) {
this.indicateDropTarget(null);
}
if (this._lastDragTarget) {
this.indicateDragTarget(null);
}
if (this._scrollInterval) {
this.win.clearInterval(this._scrollInterval);
}
},
_hoveredNode: null,
/**
@ -218,6 +280,11 @@ MarkupView.prototype = {
},
_onMouseLeave: function() {
if (this._scrollInterval) {
this.win.clearInterval(this._scrollInterval);
}
if (this.isDragging) return;
this._hideBoxModel(true);
if (this._hoveredNode) {
this.getContainer(this._hoveredNode).hovered = false;
@ -790,6 +857,10 @@ MarkupView.prototype = {
*/
_expandContainer: function(aContainer) {
return this._updateChildren(aContainer, {expand: true}).then(() => {
if (this._destroyer) {
console.warn("Could not expand the node, the markup-view was destroyed");
return;
}
aContainer.expanded = true;
});
},
@ -1361,6 +1432,12 @@ MarkupView.prototype = {
this.tooltip.destroy();
this.tooltip = null;
this.win = null;
this.doc = null;
this._lastDropTarget = null;
this._lastDragTarget = null;
return this._destroyer;
},
@ -1448,6 +1525,80 @@ MarkupView.prototype = {
this._updatePreview();
this._previewBar.classList.remove("hide");
}, 1000);
},
/**
* Takes an element as it's only argument and marks the element
* as the drop target
*/
indicateDropTarget: function(el) {
if (this._lastDropTarget) {
this._lastDropTarget.classList.remove("drop-target");
}
if (!el) return;
let target = el.classList.contains("tag-line") ?
el : el.querySelector(".tag-line") || el.closest(".tag-line");
if (!target) return;
target.classList.add("drop-target");
this._lastDropTarget = target;
},
/**
* Takes an element to mark it as indicator of dragging target's initial place
*/
indicateDragTarget: function(el) {
if (this._lastDragTarget) {
this._lastDragTarget.classList.remove("drag-target");
}
if (!el) return;
let target = el.classList.contains("tag-line") ?
el : el.querySelector(".tag-line") || el.closest(".tag-line");
if (!target) return;
target.classList.add("drag-target");
this._lastDragTarget = target;
},
/**
* Used to get the nodes required to modify the markup after dragging the element (parent/nextSibling)
*/
get dropTargetNodes() {
let target = this._lastDropTarget;
if (!target) {
return null;
}
let parent, nextSibling;
if (this._lastDropTarget.previousElementSibling &&
this._lastDropTarget.previousElementSibling.nodeName.toLowerCase() === "ul") {
parent = target.parentNode.container.node;
nextSibling = null;
} else {
parent = target.parentNode.container.node.parentNode();
nextSibling = target.parentNode.container.node;
}
if (nextSibling && nextSibling.isBeforePseudoElement) {
nextSibling = target.parentNode.parentNode.children[1].container.node;
}
if (nextSibling && nextSibling.isAfterPseudoElement) {
parent = target.parentNode.container.node.parentNode();
nextSibling = null;
}
if (parent.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) {
return null;
}
return {parent, nextSibling};
}
};
@ -1481,6 +1632,7 @@ MarkupContainer.prototype = {
this.markup = markupView;
this.node = node;
this.undo = this.markup.undo;
this.win = this.markup._frame.contentWindow;
// The template will fill the following properties
this.elt = null;
@ -1491,15 +1643,16 @@ MarkupContainer.prototype = {
this.markup.template(templateID, this);
this.elt.container = this;
// Binding event listeners
this._onMouseDown = this._onMouseDown.bind(this);
this.elt.addEventListener("mousedown", this._onMouseDown, false);
this._onToggle = this._onToggle.bind(this);
this._onMouseUp = this._onMouseUp.bind(this);
this._onMouseMove = this._onMouseMove.bind(this);
// Expanding/collapsing the node on dblclick of the whole tag-line element
// Binding event listeners
this.elt.addEventListener("mousedown", this._onMouseDown, false);
this.markup.doc.body.addEventListener("mouseup", this._onMouseUp, true);
this.markup.doc.body.addEventListener("mousemove", this._onMouseMove, true);
this.elt.addEventListener("dblclick", this._onToggle, false);
if (this.expander) {
this.expander.addEventListener("click", this._onToggle, false);
}
@ -1618,24 +1771,109 @@ MarkupContainer.prototype = {
return this.elt.parentNode ? this.elt.parentNode.container : null;
},
_isMouseDown: false,
_isDragging: false,
_dragStartY: 0,
set isDragging(isDragging) {
this._isDragging = isDragging;
this.markup.isDragging = isDragging;
if (isDragging) {
this.elt.classList.add("dragging");
this.markup.doc.body.classList.add("dragging");
} else {
this.elt.classList.remove("dragging");
this.markup.doc.body.classList.remove("dragging");
}
},
get isDragging() {
return this._isDragging;
},
_onMouseDown: function(event) {
let target = event.target;
// Target may be a resource link (generated by the output-parser)
// The "show more nodes" button (already has its onclick).
if (target.nodeName === "button") {
return;
}
// output-parser generated links handling.
if (target.nodeName === "a") {
event.stopPropagation();
event.preventDefault();
let browserWin = this.markup._inspector.target
.tab.ownerDocument.defaultView;
browserWin.openUILinkIn(target.href, "tab");
return;
}
// Or it may be the "show more nodes" button (which already has its onclick)
// Else, it's the container itself
else if (target.nodeName !== "button") {
this.hovered = false;
this.markup.navigate(this);
event.stopPropagation();
// target is the MarkupContainer itself.
this._isMouseDown = true;
this.hovered = false;
this.markup.navigate(this);
event.stopPropagation();
// Start dragging the container after a delay.
this.markup._dragStartEl = target;
this.win.setTimeout(() => {
// Make sure the mouse is still down and on target.
if (!this._isMouseDown || this.markup._dragStartEl !== target ||
this.node.isPseudoElement || this.node.isAnonymous ||
!this.win.getSelection().isCollapsed) {
return;
}
this.isDragging = true;
this._dragStartY = event.pageY;
this.markup.indicateDropTarget(this.elt);
// If this is the last child, use the closing <div.tag-line> of parent as indicator
this.markup.indicateDragTarget(this.elt.nextElementSibling ||
this.markup.getContainer(this.node.parentNode()).closeTagLine);
}, GRAB_DELAY);
},
/**
* On mouse up, stop dragging.
*/
_onMouseUp: function(event) {
this._isMouseDown = false;
if (!this.isDragging) {
return;
}
this.isDragging = false;
this.elt.style.removeProperty("top");
let dropTargetNodes = this.markup.dropTargetNodes;
if(!dropTargetNodes) {
return;
}
this.markup.walker.insertBefore(this.node, dropTargetNodes.parent,
dropTargetNodes.nextSibling);
},
/**
* On mouse move, move the dragged element if any and indicate the drop target.
*/
_onMouseMove: function(event) {
if (!this.isDragging) {
return;
}
let diff = event.pageY - this._dragStartY;
this.elt.style.top = diff + "px";
let el = this.markup.doc.elementFromPoint(event.pageX - this.win.scrollX,
event.pageY - this.win.scrollY);
this.markup.indicateDropTarget(el);
},
/**
@ -1644,7 +1882,7 @@ MarkupContainer.prototype = {
*/
flashMutation: function() {
if (!this.selected) {
let contentWin = this.markup._frame.contentWindow;
let contentWin = this.win;
this.flashed = true;
if (this._flashMutationTimer) {
contentWin.clearTimeout(this._flashMutationTimer);
@ -1777,6 +2015,10 @@ MarkupContainer.prototype = {
// Remove event listeners
this.elt.removeEventListener("mousedown", this._onMouseDown, false);
this.elt.removeEventListener("dblclick", this._onToggle, false);
this.markup.doc.body.removeEventListener("mouseup", this._onMouseUp, true);
this.markup.doc.body.removeEventListener("mousemove", this._onMouseMove, true);
this.win = null;
if (this.expander) {
this.expander.removeEventListener("click", this._onToggle, false);
@ -2515,6 +2757,17 @@ function parseAttributeValues(attr, doc) {
return attributes.reverse();
}
/**
* Map a number from one range to another.
*/
function map(value, oldMin, oldMax, newMin, newMax) {
let ratio = oldMax - oldMin;
if (ratio == 0) {
return value;
}
return newMin + (newMax - newMin) * ((value - oldMin) / ratio);
}
loader.lazyGetter(MarkupView.prototype, "strings", () => Services.strings.createBundle(
"chrome://browser/locale/devtools/inspector.properties"
));

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

@ -2,6 +2,8 @@
subsuite = devtools
support-files =
doc_markup_anonymous.html
doc_markup_dragdrop.html
doc_markup_dragdrop_autoscroll.html
doc_markup_edit.html
doc_markup_events.html
doc_markup_events_jquery.html
@ -37,6 +39,11 @@ skip-if = e10s # scratchpad.xul is not loading in e10s window
[browser_markupview_anonymous_04.js]
[browser_markupview_copy_image_data.js]
[browser_markupview_css_completion_style_attribute.js]
[browser_markupview_dragdrop_autoscroll.js]
[browser_markupview_dragdrop_invalidNodes.js]
[browser_markupview_dragdrop_isDragging.js]
[browser_markupview_dragdrop_reorder.js]
[browser_markupview_dragdrop_textSelection.js]
[browser_markupview_events.js]
skip-if = e10s # Bug 1040751 - CodeMirror editor.destroy() isn't e10s compatible
[browser_markupview_events-overflow.js]

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

@ -0,0 +1,61 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test: Dragging nodes near top/bottom edges of inspector
// should auto-scroll
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop_autoscroll.html";
const GRAB_DELAY = 400;
add_task(function*() {
let {inspector} = yield addTab(TEST_URL).then(openInspector);
let markup = inspector.markup;
let container = yield getContainerForSelector("#first", inspector);
let rect = container.elt.getBoundingClientRect();
info("Simulating mouseDown on #first");
container._onMouseDown({
target: container.tagLine,
pageX: 10,
pageY: rect.top,
stopPropagation: function() {},
preventDefault: function() {}
});
yield wait(GRAB_DELAY + 1);
let clientHeight = markup.doc.documentElement.clientHeight;
info("Simulating mouseMove on #first with pageY: " + clientHeight);
let ev = {
target: container.tagLine,
pageX: 10,
pageY: clientHeight,
preventDefault: function() {}
};
info("Listening on scroll event");
let scroll = onScroll(markup.win);
markup._onMouseMove(ev);
yield scroll;
container._onMouseUp(ev);
markup._onMouseUp(ev);
ok("Scroll event fired");
});
function onScroll(win) {
return new Promise((resolve, reject) => {
win.onscroll = function(e) {
resolve(e);
}
});
};

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

@ -0,0 +1,58 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test: pseudo-elements and anonymous nodes should not be draggable
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html";
const GRAB_DELAY = 400;
add_task(function*() {
Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true);
let {inspector} = yield addTab(TEST_URL).then(openInspector);
info("Expanding #test");
let parentFront = yield getNodeFront("#test", inspector);
yield inspector.markup.expandNode(parentFront);
yield waitForMultipleChildrenUpdates(inspector);
let parentContainer = yield getContainerForNodeFront(parentFront, inspector);
let beforePseudo = parentContainer.elt.children[1].firstChild.container;
parentContainer.elt.scrollIntoView(true);
info("Simulating mouseDown on #test::before");
beforePseudo._onMouseDown({
target: beforePseudo.tagLine,
stopPropagation: function() {},
preventDefault: function() {}
});
info("Waiting " + (GRAB_DELAY + 1) + "ms")
yield wait(GRAB_DELAY + 1);
is(beforePseudo.isDragging, false, "[pseudo-element] isDragging is false after GRAB_DELAY has passed");
let inputFront = yield getNodeFront("#anonymousParent", inspector);
yield inspector.markup.expandNode(inputFront);
yield waitForMultipleChildrenUpdates(inspector);
let inputContainer = yield getContainerForNodeFront(inputFront, inspector);
let anonymousDiv = inputContainer.elt.children[1].firstChild.container;
inputContainer.elt.scrollIntoView(true);
info("Simulating mouseDown on input#anonymousParent div");
anonymousDiv._onMouseDown({
target: anonymousDiv.tagLine,
stopPropagation: function() {},
preventDefault: function() {}
});
info("Waiting " + (GRAB_DELAY + 1) + "ms")
yield wait(GRAB_DELAY + 1);
is(anonymousDiv.isDragging, false, "[anonymous element] isDragging is false after GRAB_DELAY has passed");
});

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

@ -0,0 +1,41 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test drag mode's delay, it shouldn't enable dragging before
// GRAB_DELAY = 400 has passed
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html";
const GRAB_DELAY = 400;
add_task(function*() {
let {inspector} = yield addTab(TEST_URL).then(openInspector);
let el = yield getContainerForSelector("#test", inspector);
let rect = el.tagLine.getBoundingClientRect();
info("Simulating mouseDown on #test");
el._onMouseDown({
target: el.tagLine,
pageX: rect.x,
pageY: rect.y,
stopPropagation: function() {}
});
is(el.isDragging, false, "isDragging should not be set to true immedietly");
info("Waiting " + (GRAB_DELAY + 1) + "ms");
yield wait(GRAB_DELAY + 1);
ok(el.isDragging, "isDragging true after GRAB_DELAY has passed");
info("Simulating mouseUp on #test");
el._onMouseUp({
target: el.tagLine,
pageX: rect.x,
pageY: rect.y
});
is(el.isDragging, false, "isDragging false after mouseUp");
});

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

@ -0,0 +1,109 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test different kinds of drag and drop node re-ordering
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html";
const GRAB_DELAY = 400;
add_task(function*() {
let {inspector} = yield addTab(TEST_URL).then(openInspector);
info("Expanding #test");
let parentFront = yield getNodeFront("#test", inspector);
let parent = yield getNode("#test");
let parentContainer = yield getContainerForNodeFront(parentFront, inspector);
yield inspector.markup.expandNode(parentFront);
yield waitForMultipleChildrenUpdates(inspector);
parentContainer.elt.scrollIntoView(true);
info("Testing switching elements inside their parent");
yield moveElementDown("#firstChild", "#middleChild", inspector);
is(parent.children[0].id, "middleChild", "#firstChild is now the second child of #test");
is(parent.children[1].id, "firstChild", "#middleChild is now the first child of #test");
info("Testing switching elements with a last child");
yield moveElementDown("#firstChild", "#lastChild", inspector);
is(parent.children[1].id, "lastChild", "#lastChild is now the second child of #test");
is(parent.children[2].id, "firstChild", "#firstChild is now the last child of #test");
info("Testing appending element to a parent");
yield moveElementDown("#before", "#test", inspector);
is(parent.children.length, 4, "New element appended to #test");
is(parent.children[0].id, "before", "New element is appended at the right place (currently first child)");
info("Testing moving element to after it's parent");
yield moveElementDown("#firstChild", "#test", inspector);
is(parent.children.length, 3, "#firstChild is no longer #test's child");
is(parent.nextElementSibling.id, "firstChild", "#firstChild is now #test's nextElementSibling");
});
function* dragContainer(selector, targetOffset, inspector) {
info("Dragging the markup-container for node " + selector);
let container = yield getContainerForSelector(selector, inspector);
let updated = inspector.once("inspector-updated");
let rect = {
x: container.tagLine.offsetLeft,
y: container.tagLine.offsetTop
};
info("Simulating mouseDown on " + selector);
container._onMouseDown({
target: container.tagLine,
pageX: rect.x,
pageY: rect.y,
stopPropagation: function() {}
});
let targetX = rect.x + targetOffset.x,
targetY = rect.y + targetOffset.y;
setTimeout(() => {
info("Simulating mouseMove on " + selector +
" with pageX: " + targetX + " pageY: " + targetY);
container._onMouseMove({
target: container.tagLine,
pageX: targetX,
pageY: targetY
});
info("Simulating mouseUp on " + selector +
" with pageX: " + targetX + " pageY: " + targetY);
container._onMouseUp({
target: container.tagLine,
pageX: targetX,
pageY: targetY
});
container.markup._onMouseUp();
}, GRAB_DELAY+1);
return updated;
};
function* moveElementDown(selector, next, inspector) {
let onMutated = inspector.once("markupmutation");
let uiUpdate = inspector.once("inspector-updated");
let el = yield getContainerForSelector(next, inspector);
let height = el.tagLine.getBoundingClientRect().height;
info("Switching " + selector + ' with ' + next);
yield dragContainer(selector, {x: 0, y: Math.round(height) + 2}, inspector);
yield onMutated;
yield uiUpdate;
};

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

@ -0,0 +1,48 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test: Nodes should not be draggable if there is a text selected
// (trying to move selected text around shouldn't trigger node drag and drop)
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html";
const GRAB_DELAY = 400;
add_task(function*() {
let {inspector} = yield addTab(TEST_URL).then(openInspector);
let markup = inspector.markup;
info("Expanding span#before");
let spanFront = yield getNodeFront("#before", inspector);
let spanContainer = yield getContainerForNodeFront(spanFront, inspector);
let span = yield getNode("#before");
yield inspector.markup.expandNode(spanFront);
yield waitForMultipleChildrenUpdates(inspector);
spanContainer.elt.scrollIntoView(true);
info("Selecting #before's text content");
let textContent = spanContainer.elt.children[1].firstChild.container;
let selectRange = markup.doc.createRange();
selectRange.selectNode(textContent.editor.elt.querySelector('[tabindex]'));
markup.doc.getSelection().addRange(selectRange);
info("Simulating mouseDown on #before");
spanContainer._onMouseDown({
pageX: 0,
pageY: 0,
target: spanContainer.tagLine,
stopPropagation: function() {},
preventDefault: function() {}
});
yield wait(GRAB_DELAY + 1);
is(spanContainer.isDragging, false, "isDragging should be false if there is a text selected");
});

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

@ -31,15 +31,3 @@ add_task(function*() {
yield inspector.once("inspector-updated");
});
// The expand all operation of the markup-view calls itself recursively and
// there's not one event we can wait for to know when it's done
function* waitForMultipleChildrenUpdates(inspector) {
// As long as child updates are queued up while we wait for an update already
// wait again
if (inspector.markup._queuedChildUpdates &&
inspector.markup._queuedChildUpdates.size) {
yield waitForChildrenUpdated(inspector);
return yield waitForMultipleChildrenUpdates(inspector);
}
}

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

@ -33,15 +33,3 @@ add_task(function*() {
"Container for node " + nodeFront.tagName + " is expanded");
}
});
// The expand all operation of the markup-view calls itself recursively and
// there's not one event we can wait for to know when it's done
function* waitForMultipleChildrenUpdates(inspector) {
// As long as child updates are queued up while we wait for an update already
// wait again
if (inspector.markup._queuedChildUpdates &&
inspector.markup._queuedChildUpdates.size) {
yield waitForChildrenUpdated(inspector);
return yield waitForMultipleChildrenUpdates(inspector);
}
}

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

@ -0,0 +1,31 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=858038
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 858038</title>
<style>
#test::before {
content: 'This should not be draggable';
}
</style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=858038">Mozilla Bug 858038</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<input id="anonymousParent" />
<span id="before">Before</span>
<pre id="test">
<span id="firstChild">First</span>
<span id="middleChild">Middle</span>
<span id="lastChild">Last</span>
</pre>
<span id="after">After</span>
</body>
</html>

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

@ -0,0 +1,51 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=858038
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 858038 - Autoscroll</title>
</head>
<body>
<div id="first"></div>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=858038">Mozilla Bug 858038</a>
<p id="display">Test</p>
<div id="content" style="display: none">
</div>
<!-- Make sure the markup-view has enough nodes shown by default that it has a scrollbar -->
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</body>
</html>

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

@ -561,3 +561,16 @@ function checkFocusedAttribute(attrName, editMode) {
editMode ? "input": "span",
editMode ? attrName + " is in edit mode" : attrName + " is not in edit mode");
}
// The expand all operation of the markup-view calls itself recursively and
// there's not one event we can wait for to know when it's done
// so use this helper function to wait until all recursive children updates are done.
function* waitForMultipleChildrenUpdates(inspector) {
// As long as child updates are queued up while we wait for an update already
// wait again
if (inspector.markup._queuedChildUpdates &&
inspector.markup._queuedChildUpdates.size) {
yield waitForChildrenUpdated(inspector);
return yield waitForMultipleChildrenUpdates(inspector);
}
}

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

@ -35,9 +35,6 @@ function test() {
function testPrefs() {
let { Prefs } = aMonitor.panelWin;
is(Prefs._root, "devtools.netmonitor",
"The preferences object should have a correct root path.");
is(Prefs.networkDetailsWidth,
Services.prefs.getIntPref("devtools.netmonitor.panes-network-details-width"),
"Getting a pref should work correctly.");

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

@ -3,6 +3,8 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Cc, Ci, Cu, Cr } = require("chrome");
/**
* Utility functions for managing recording models and their internal data,
* such as filtering profile samples or offsetting timestamps.
@ -130,3 +132,50 @@ exports.RecordingUtils.getSamplesFromAllocations = function(allocations) {
gSamplesFromAllocationCache.set(allocations, samples);
return samples;
}
/**
* Gets the current timeline blueprint without the hidden markers.
*
* @param blueprint
* The default timeline blueprint.
* @param array hiddenMarkers
* A list of hidden markers' names.
* @return object
* The filtered timeline blueprint.
*/
exports.RecordingUtils.getFilteredBlueprint = function({ blueprint, hiddenMarkers }) {
let filteredBlueprint = Cu.cloneInto(blueprint, {});
let maybeRemovedGroups = new Set();
let removedGroups = new Set();
// 1. Remove hidden markers from the blueprint.
for (let hiddenMarkerName of hiddenMarkers) {
maybeRemovedGroups.add(filteredBlueprint[hiddenMarkerName].group);
delete filteredBlueprint[hiddenMarkerName];
}
// 2. Get a list of all the groups that will be removed.
for (let maybeRemovedGroup of maybeRemovedGroups) {
let markerNames = Object.keys(filteredBlueprint);
let isGroupRemoved = markerNames.every(e => filteredBlueprint[e].group != maybeRemovedGroup);
if (isGroupRemoved) {
removedGroups.add(maybeRemovedGroup);
}
}
// 3. Offset groups so that their indices are consecutive.
for (let removedGroup of removedGroups) {
let markerNames = Object.keys(filteredBlueprint);
for (let markerName of markerNames) {
let markerDetails = filteredBlueprint[markerName];
if (markerDetails.group > removedGroup) {
markerDetails.group--;
}
}
}
return filteredBlueprint;
};

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

@ -57,10 +57,12 @@ const BRANCH_NAME = "devtools.performance.ui.";
// Events emitted by various objects in the panel.
const EVENTS = {
// Fired by the OptionsView when a preference changes.
// Fired by the PerformanceController and OptionsView when a pref changes.
PREF_CHANGED: "Performance:PrefChanged",
// Emitted by the PerformanceView when the state (display mode) changes.
// Emitted by the PerformanceView when the state (display mode) changes,
// for example when switching between "empty", "recording" or "recorded".
// This causes certain panels to be hidden or visible.
UI_STATE_CHANGED: "Performance:UI:StateChanged",
// Emitted by the PerformanceView on clear button click
@ -176,6 +178,16 @@ let PerformanceController = {
this._onRecordingSelectFromView = this._onRecordingSelectFromView.bind(this);
this._onPrefChanged = this._onPrefChanged.bind(this);
// All boolean prefs should be handled via the OptionsView in the
// ToolbarView, so that they may be accessible via the "gear" menu.
// Every other pref should be registered here.
this._nonBooleanPrefs = new ViewHelpers.Prefs("devtools.performance", {
"hidden-markers": ["Json", "timeline.hidden-markers"]
});
this._nonBooleanPrefs.registerObserver();
this._nonBooleanPrefs.on("pref-changed", this._onPrefChanged);
ToolbarView.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
@ -195,6 +207,9 @@ let PerformanceController = {
* Remove events handled by the PerformanceController
*/
destroy: function() {
this._nonBooleanPrefs.unregisterObserver();
this._nonBooleanPrefs.off("pref-changed", this._onPrefChanged);
ToolbarView.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
@ -211,11 +226,32 @@ let PerformanceController = {
},
/**
* Get a preference setting from `prefName` via the underlying
* Get a boolean preference setting from `prefName` via the underlying
* OptionsView in the ToolbarView.
*
* @param string prefName
* @return boolean
*/
getOption: function (prefName) {
return ToolbarView.optionsView.getPref(prefName);
},
/**
* Get a preference setting from `prefName`.
* @param string prefName
* @return object
*/
getPref: function (prefName) {
return ToolbarView.optionsView.getPref(prefName);
return this._nonBooleanPrefs[prefName];
},
/**
* Set a preference setting from `prefName`.
* @param string prefName
* @param object prefValue
*/
setPref: function (prefName, prefValue) {
this._nonBooleanPrefs[prefName] = prefValue;
},
/**
@ -223,9 +259,9 @@ let PerformanceController = {
* when the front has started to record.
*/
startRecording: Task.async(function *() {
let withMemory = this.getPref("enable-memory");
let withTicks = this.getPref("enable-framerate");
let withAllocations = this.getPref("enable-memory");
let withMemory = this.getOption("enable-memory");
let withTicks = this.getOption("enable-framerate");
let withAllocations = this.getOption("enable-memory");
let recording = this._createRecording({ withMemory, withTicks, withAllocations });
@ -241,7 +277,7 @@ let PerformanceController = {
* when the front has stopped recording.
*/
stopRecording: Task.async(function *() {
let recording = this._getLatestRecording();
let recording = this.getLatestRecording();
this.emit(EVENTS.RECORDING_WILL_STOP, recording);
yield recording.stopRecording();
@ -267,7 +303,7 @@ let PerformanceController = {
* Emits `EVENTS.RECORDINGS_CLEARED` when complete so other components can clean up.
*/
clearRecordings: Task.async(function* () {
let latest = this._getLatestRecording();
let latest = this.getLatestRecording();
if (latest && latest.isRecording()) {
yield this.stopRecording();
@ -335,13 +371,23 @@ let PerformanceController = {
* Get most recently added recording that was triggered manually (via UI).
* @return RecordingModel
*/
_getLatestRecording: function () {
getLatestRecording: function () {
for (let i = this._recordings.length - 1; i >= 0; i--) {
return this._recordings[i];
}
return null;
},
/**
* Gets the current timeline blueprint without the hidden markers.
* @return object
*/
getTimelineBlueprint: function() {
let blueprint = TIMELINE_BLUEPRINT;
let hiddenMarkers = this.getPref("hidden-markers");
return RecordingUtils.getFilteredBlueprint({ blueprint, hiddenMarkers });
},
/**
* Fired whenever the PerformanceFront emits markers, memory or ticks.
*/
@ -362,8 +408,8 @@ let PerformanceController = {
* Fired when the ToolbarView fires a PREF_CHANGED event.
* with the value.
*/
_onPrefChanged: function (_, prefName, value) {
this.emit(EVENTS.PREF_CHANGED, prefName, value);
_onPrefChanged: function (_, prefName, prefValue) {
this.emit(EVENTS.PREF_CHANGED, prefName, prefValue);
},
toString: () => "[object PerformanceController]"

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

@ -29,6 +29,7 @@
<script type="application/javascript" src="performance/views/recordings.js"/>
<popupset id="performance-options-popupset">
<menupopup id="performance-filter-menupopup"/>
<menupopup id="performance-options-menupopup">
<menuitem id="option-show-platform-data"
type="checkbox"
@ -85,6 +86,12 @@
<vbox id="performance-pane" flex="1">
<toolbar id="performance-toolbar" class="devtools-toolbar">
<hbox id="performance-toolbar-control-other" class="devtools-toolbarbutton-group">
<toolbarbutton id="filter-button"
popup="performance-filter-menupopup"
class="devtools-toolbarbutton"
tooltiptext="&profilerUI.options.filter.tooltiptext;"/>
</hbox>
<hbox id="performance-toolbar-controls-detail-views" class="devtools-toolbarbutton-group">
<toolbarbutton id="select-waterfall-view"
class="devtools-toolbarbutton devtools-button"
@ -112,7 +119,7 @@
<toolbarbutton id="performance-options-button"
class="devtools-toolbarbutton devtools-option-toolbarbutton"
popup="performance-options-menupopup"
tooltiptext="&profilerUI.options.tooltiptext;"/>
tooltiptext="&profilerUI.options.gear.tooltiptext;"/>
</hbox>
</toolbar>

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

@ -97,3 +97,4 @@ support-files =
[browser_profiler_tree-view-06.js]
[browser_profiler_tree-view-07.js]
[browser_timeline_blueprint.js]
[browser_timeline_filters.js]

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

@ -5,28 +5,29 @@
* Tests markers filtering mechanism.
*/
add_task(function*() {
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
let { $, $$, TimelineController, TimelineView } = panel.panelWin;
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { $, $$, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
let { MARKERS_GRAPH_ROW_HEIGHT } = panel.panelWin;
yield TimelineController.toggleRecording();
yield startRecording(panel);
ok(true, "Recording has started.");
yield waitUntil(() => {
// Wait until we get 3 different markers.
let markers = TimelineController.getMarkers();
let markers = PerformanceController.getCurrentRecording().getMarkers();
return markers.some(m => m.name == "Styles") &&
markers.some(m => m.name == "Reflow") &&
markers.some(m => m.name == "Paint");
});
yield TimelineController.toggleRecording();
yield stopRecording(panel);
let overview = TimelineView.markersOverview;
let waterfall = TimelineView.waterfall;
let overview = OverviewView.markersOverview;
let waterfall = WaterfallView.waterfall;
// Select everything
overview.setSelection({ start: 0, end: overview.width })
OverviewView.setTimeInterval({ startTime: 0, endTime: Number.MAX_VALUE })
$("#filter-button").click();
@ -70,8 +71,7 @@ add_task(function*() {
yield waitUntil(() => !waterfall._outstandingMarkers.length);
// A row is 11px. See markers-overview.js
is(overview.fixedHeight, heightBefore - 11, "Overview is smaller");
is(overview.fixedHeight, heightBefore - MARKERS_GRAPH_ROW_HEIGHT, "Overview is smaller");
ok(!$(".waterfall-marker-bar[type=Styles]"), "No 'Styles' marker (4)");
ok(!$(".waterfall-marker-bar[type=Reflow]"), "No 'Reflow' marker (4)");
ok(!$(".waterfall-marker-bar[type=Paint]"), "No 'Paint' marker (4)");
@ -89,5 +89,6 @@ add_task(function*() {
is(overview.fixedHeight, originalHeight, "Overview restored");
$(".waterfall-marker-bar[type=Styles]");
});
yield teardown(panel);
finish();
}

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

@ -5,13 +5,16 @@
<html>
<head>
<meta charset="utf-8"/>
<title>Profiler test page</title>
<title>Performance test page</title>
</head>
<body>
<script type="text/javascript">
var x = 1;
function test() {
var a = "Hello world!";
document.body.style.borderTop = x + "px solid red";
x = 1^x;
document.body.innerHeight; // flush pending reflows
}
// Prevent this script from being garbage collected.

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

@ -59,10 +59,16 @@ let DetailsSubview = {
/**
* An array of preferences under `devtools.performance.ui.` that the view should
* rerender upon change.
* rerender and callback `this._onRerenderPrefChanged` upon change.
*/
rerenderPrefs: [],
/**
* An array of preferences under `devtools.performance.` that the view should
* observe and callback `this._onObservedPrefChange` upon change.
*/
observedPrefs: [],
/**
* Called when recording stops or is selected.
*/
@ -103,6 +109,10 @@ let DetailsSubview = {
* Fired when a preference in `devtools.performance.ui.` is changed.
*/
_onPrefChanged: function (_, prefName) {
if (~this.observedPrefs.indexOf(prefName) && this._onObservedPrefChange) {
this._onObservedPrefChange(_, prefName);
}
// All detail views require a recording to be complete, so do not
// attempt to render if recording is in progress or does not exist.
let recording = PerformanceController.getCurrentRecording();
@ -115,7 +125,7 @@ let DetailsSubview = {
}
if (this._onRerenderPrefChanged) {
this._onRerenderPrefChanged();
this._onRerenderPrefChanged(_, prefName);
}
if (DetailsView.isViewSelected(this) || this.canUpdateWhileHidden) {

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

@ -64,8 +64,8 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
*/
_prepareCallTree: function (profile, { startTime, endTime }, options) {
let threadSamples = profile.threads[0].samples;
let contentOnly = !PerformanceController.getPref("show-platform-data");
let invertTree = PerformanceController.getPref("invert-call-tree");
let contentOnly = !PerformanceController.getOption("show-platform-data");
let invertTree = PerformanceController.getOption("invert-call-tree");
let threadNode = new ThreadNode(threadSamples,
{ startTime, endTime, contentOnly, invertTree });
@ -108,7 +108,7 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
// When platform data isn't shown, hide the cateogry labels, since they're
// only available for C++ frames.
let contentOnly = !PerformanceController.getPref("show-platform-data");
let contentOnly = !PerformanceController.getOption("show-platform-data");
root.toggleCategories(!contentOnly);
},

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

@ -53,10 +53,10 @@ let JsFlameGraphView = Heritage.extend(DetailsSubview, {
let samples = profile.threads[0].samples;
let data = FlameGraphUtils.createFlameGraphDataFromSamples(samples, {
invertStack: PerformanceController.getPref("invert-flame-graph"),
flattenRecursion: PerformanceController.getPref("flatten-tree-recursion"),
filterFrames: !PerformanceController.getPref("show-platform-data") && FrameNode.isContent,
showIdleBlocks: PerformanceController.getPref("show-idle-blocks") && L10N.getStr("table.idle")
invertStack: PerformanceController.getOption("invert-flame-graph"),
flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
filterFrames: !PerformanceController.getOption("show-platform-data") && FrameNode.isContent,
showIdleBlocks: PerformanceController.getOption("show-idle-blocks") && L10N.getStr("table.idle")
});
this.graph.setData({ data,

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

@ -62,7 +62,7 @@ let MemoryCallTreeView = Heritage.extend(DetailsSubview, {
*/
_prepareCallTree: function (allocations, { startTime, endTime }, options) {
let samples = RecordingUtils.getSamplesFromAllocations(allocations);
let invertTree = PerformanceController.getPref("invert-call-tree");
let invertTree = PerformanceController.getOption("invert-call-tree");
let threadNode = new ThreadNode(samples,
{ startTime, endTime, invertTree });

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

@ -52,9 +52,9 @@ let MemoryFlameGraphView = Heritage.extend(DetailsSubview, {
let samples = RecordingUtils.getSamplesFromAllocations(allocations);
let data = FlameGraphUtils.createFlameGraphDataFromSamples(samples, {
invertStack: PerformanceController.getPref("invert-flame-graph"),
flattenRecursion: PerformanceController.getPref("flatten-tree-recursion"),
showIdleBlocks: PerformanceController.getPref("show-idle-blocks") && L10N.getStr("table.idle")
invertStack: PerformanceController.getOption("invert-flame-graph"),
flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
showIdleBlocks: PerformanceController.getOption("show-idle-blocks") && L10N.getStr("table.idle")
});
this.graph.setData({ data,

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

@ -8,6 +8,14 @@
*/
let WaterfallView = Heritage.extend(DetailsSubview, {
observedPrefs: [
"hidden-markers"
],
rerenderPrefs: [
"hidden-markers"
],
rangeChangeDebounceTime: 10, // ms
/**
@ -16,7 +24,7 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
initialize: function () {
DetailsSubview.initialize.call(this);
this.waterfall = new Waterfall($("#waterfall-breakdown"), $("#waterfall-view"), TIMELINE_BLUEPRINT);
this.waterfall = new Waterfall($("#waterfall-breakdown"), $("#waterfall-view"));
this.details = new MarkerDetails($("#waterfall-details"), $("#waterfall-view > splitter"));
this._onMarkerSelected = this._onMarkerSelected.bind(this);
@ -26,6 +34,8 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
this.waterfall.on("unselected", this._onMarkerSelected);
this.details.on("resize", this._onResize);
let blueprint = PerformanceController.getTimelineBlueprint();
this.waterfall.setBlueprint(blueprint);
this.waterfall.recalculateBounds();
},
@ -79,5 +89,13 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
this.render();
},
/**
* Called whenever an observed pref is changed.
*/
_onObservedPrefChange: function(_, prefName) {
let blueprint = PerformanceController.getTimelineBlueprint();
this.waterfall.setBlueprint(blueprint);
},
toString: () => "[object WaterfallView]"
});

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

@ -4,19 +4,41 @@
"use strict";
/**
* Details view containing profiler call tree and markers waterfall. Manages
* subviews and toggles visibility between them.
* Details view containing call trees, flamegraphs and markers waterfall.
* Manages subviews and toggles visibility between them.
*/
let DetailsView = {
/**
* Name to node+object mapping of subviews.
* Name to (node id, view object, actor requirements, pref killswitch)
* mapping of subviews.
*/
components: {
"waterfall": { id: "waterfall-view", view: WaterfallView, requires: ["timeline"] },
"js-calltree": { id: "js-calltree-view", view: JsCallTreeView },
"js-flamegraph": { id: "js-flamegraph-view", view: JsFlameGraphView, requires: ["timeline"] },
"memory-calltree": { id: "memory-calltree-view", view: MemoryCallTreeView, requires: ["memory"], pref: "enable-memory" },
"memory-flamegraph": { id: "memory-flamegraph-view", view: MemoryFlameGraphView, requires: ["memory", "timeline"], pref: "enable-memory" }
"waterfall": {
id: "waterfall-view",
view: WaterfallView,
requires: ["timeline"]
},
"js-calltree": {
id: "js-calltree-view",
view: JsCallTreeView
},
"js-flamegraph": {
id: "js-flamegraph-view",
view: JsFlameGraphView,
requires: ["timeline"]
},
"memory-calltree": {
id: "memory-calltree-view",
view: MemoryCallTreeView,
requires: ["memory"],
pref: "enable-memory"
},
"memory-flamegraph": {
id: "memory-flamegraph-view",
view: MemoryFlameGraphView,
requires: ["memory", "timeline"],
pref: "enable-memory"
}
},
/**
@ -66,12 +88,13 @@ let DetailsView = {
*/
setAvailableViews: Task.async(function* () {
let mocks = gFront.getMocksInUse();
for (let [name, { view, pref, requires }] of Iterator(this.components)) {
let recording = PerformanceController.getCurrentRecording();
let isRecorded = recording && !recording.isRecording();
// View is enabled view prefs
let isEnabled = !pref || PerformanceController.getPref(pref);
// View is enabled by its corresponding pref
let isEnabled = !pref || PerformanceController.getOption(pref);
// View is supported by the server actor, and the requried actor is not being mocked
let isSupported = !requires || requires.every(r => !mocks[r]);

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

@ -40,8 +40,8 @@ let OverviewView = {
// Toggle the initial visibility of memory and framerate graph containers
// based off of prefs.
$("#memory-overview").hidden = !PerformanceController.getPref("enable-memory");
$("#time-framerate").hidden = !PerformanceController.getPref("enable-framerate");
$("#memory-overview").hidden = !PerformanceController.getOption("enable-memory");
$("#time-framerate").hidden = !PerformanceController.getOption("enable-framerate");
PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
PerformanceController.on(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
@ -55,6 +55,16 @@ let OverviewView = {
* Unbinds events.
*/
destroy: function () {
if (this.markersOverview) {
this.markersOverview.destroy();
}
if (this.memoryOverview) {
this.memoryOverview.destroy();
}
if (this.framerateGraph) {
this.framerateGraph.destroy();
}
PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
PerformanceController.off(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
@ -86,7 +96,7 @@ let OverviewView = {
* Sets the time interval selection for all graphs in this overview.
*
* @param object interval
* The { starTime, endTime }, in milliseconds.
* The { startTime, endTime }, in milliseconds.
*/
setTimeInterval: function(interval, options = {}) {
if (this.isDisabled()) {
@ -138,7 +148,8 @@ let OverviewView = {
yield this.markersOverview.ready();
return true;
}
this.markersOverview = new MarkersOverview($("#markers-overview"), TIMELINE_BLUEPRINT);
let blueprint = PerformanceController.getTimelineBlueprint();
this.markersOverview = new MarkersOverview($("#markers-overview"), blueprint);
this.markersOverview.headerHeight = MARKERS_GRAPH_HEADER_HEIGHT;
this.markersOverview.rowHeight = MARKERS_GRAPH_ROW_HEIGHT;
this.markersOverview.groupPadding = MARKERS_GROUP_VERTICAL_PADDING;
@ -155,7 +166,7 @@ let OverviewView = {
* ready to use, `false` if the graph is disabled.
*/
_memoryGraphAvailable: Task.async(function *() {
if (!PerformanceController.getPref("enable-memory")) {
if (!PerformanceController.getOption("enable-memory")) {
return false;
}
if (this.memoryOverview) {
@ -179,7 +190,7 @@ let OverviewView = {
* ready to use, `false` if the graph is disabled.
*/
_framerateGraphAvailable: Task.async(function *() {
if (!PerformanceController.getPref("enable-framerate")) {
if (!PerformanceController.getOption("enable-framerate")) {
return false;
}
if (this.framerateGraph) {
@ -340,14 +351,26 @@ let OverviewView = {
* Called whenever a preference in `devtools.performance.ui.` changes. Used
* to toggle the visibility of memory and framerate graphs.
*/
_onPrefChanged: function (_, prefName) {
if (prefName === "enable-memory") {
$("#memory-overview").hidden = !PerformanceController.getPref("enable-memory");
_onPrefChanged: Task.async(function* (_, prefName, prefValue) {
switch (prefName) {
case "enable-memory": {
$("#memory-overview").hidden = !prefValue;
break;
}
case "enable-framerate": {
$("#time-framerate").hidden = !prefValue;
break;
}
case "hidden-markers": {
if (yield this._markersGraphAvailable()) {
let blueprint = PerformanceController.getTimelineBlueprint();
this.markersOverview.setBlueprint(blueprint);
this.markersOverview.refresh({ force: true });
}
break;
}
}
if (prefName === "enable-framerate") {
$("#time-framerate").hidden = !PerformanceController.getPref("enable-framerate");
}
},
}),
toString: () => "[object OverviewView]"
};

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

@ -11,6 +11,9 @@ let ToolbarView = {
* Sets up the view with event binding.
*/
initialize: Task.async(function *() {
this._onFilterPopupShowing = this._onFilterPopupShowing.bind(this);
this._onFilterPopupHiding = this._onFilterPopupHiding.bind(this);
this._onHiddenMarkersChanged = this._onHiddenMarkersChanged.bind(this);
this._onPrefChanged = this._onPrefChanged.bind(this);
this.optionsView = new OptionsView({
@ -20,16 +23,87 @@ let ToolbarView = {
yield this.optionsView.initialize();
this.optionsView.on("pref-changed", this._onPrefChanged);
this._buildMarkersFilterPopup();
this._updateHiddenMarkersPopup();
$("#performance-filter-menupopup").addEventListener("popupshowing", this._onFilterPopupShowing);
$("#performance-filter-menupopup").addEventListener("popuphiding", this._onFilterPopupHiding);
}),
/**
* Unbinds events and cleans up view.
*/
destroy: function () {
$("#performance-filter-menupopup").removeEventListener("popupshowing", this._onFilterPopupShowing);
$("#performance-filter-menupopup").removeEventListener("popuphiding", this._onFilterPopupHiding);
this.optionsView.off("pref-changed", this._onPrefChanged);
this.optionsView.destroy();
},
/**
* Creates the timeline markers filter popup.
*/
_buildMarkersFilterPopup: function() {
for (let [markerName, markerDetails] of Iterator(TIMELINE_BLUEPRINT)) {
let menuitem = document.createElement("menuitem");
menuitem.setAttribute("closemenu", "none");
menuitem.setAttribute("type", "checkbox");
menuitem.setAttribute("align", "center");
menuitem.setAttribute("flex", "1");
menuitem.setAttribute("label", markerDetails.label);
menuitem.setAttribute("marker-type", markerName);
menuitem.addEventListener("command", this._onHiddenMarkersChanged);
// Style used by pseudo element ::before in performance.inc.css
let bulletStyle = `--bullet-bg: ${markerDetails.fill};`
bulletStyle += `--bullet-border: ${markerDetails.stroke}`;
menuitem.setAttribute("style", bulletStyle);
$("#performance-filter-menupopup").appendChild(menuitem);
}
},
/**
* Updates the menu items checked state in the timeline markers filter popup.
*/
_updateHiddenMarkersPopup: function() {
let menuItems = $$("#performance-filter-menupopup menuitem[marker-type]");
let hiddenMarkers = PerformanceController.getPref("hidden-markers");
for (let menuitem of menuItems) {
if (~hiddenMarkers.indexOf(menuitem.getAttribute("marker-type"))) {
menuitem.removeAttribute("checked");
} else {
menuitem.setAttribute("checked", "true");
}
}
},
/**
* Fired when the markers filter popup starts to show.
*/
_onFilterPopupShowing: function() {
$("#filter-button").setAttribute("open", "true");
},
/**
* Fired when the markers filter popup starts to hide.
*/
_onFilterPopupHiding: function() {
$("#filter-button").removeAttribute("open");
},
/**
* Fired when a menu item in the markers filter popup is checked or unchecked.
*/
_onHiddenMarkersChanged: function() {
let checkedMenuItems = $$("#performance-filter-menupopup menuitem[marker-type]:not([checked])");
let hiddenMarkers = Array.map(checkedMenuItems, e => e.getAttribute("marker-type"));
PerformanceController.setPref("hidden-markers", hiddenMarkers);
},
/**
* Fired when a preference changes in the underlying OptionsView.
* Propogated by the PerformanceController.

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

@ -148,8 +148,6 @@ let PrefObserver = {
this.branch.removeObserver("", this);
},
observe: function(subject, topic, pref) {
Prefs.refresh();
if (pref == "ui.show-platform-data") {
RecordingsListView.forceSelect(RecordingsListView.selectedItem);
}

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

@ -20,14 +20,23 @@ function test() {
"The pref was was correctly changed (2).");
Services.prefs.setBoolPref("devtools.debugger.enabled", originalPrefValue);
info("The pref value was reset.");
info("The pref value was reset (1).");
is(Prefs.foo, !originalPrefValue,
"The cached pref value hasn't changed yet.");
"The cached pref value hasn't changed yet (1).");
Prefs.refresh();
Services.prefs.setBoolPref("devtools.debugger.enabled", !originalPrefValue);
info("The pref value was reset (2).");
is(Prefs.foo, !originalPrefValue,
"The cached pref value hasn't changed yet (2).");
Prefs.registerObserver();
Services.prefs.setBoolPref("devtools.debugger.enabled", originalPrefValue);
info("The pref value was reset (3).");
is(Prefs.foo, originalPrefValue,
"The cached pref value has changed now.");
Prefs.unregisterObserver();
finish();
}

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

@ -17,6 +17,7 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
Cu.import("resource://gre/modules/devtools/event-emitter.js");
this.EXPORTED_SYMBOLS = [
"Heritage", "ViewHelpers", "WidgetMethods",
@ -392,25 +393,57 @@ ViewHelpers.L10N.prototype = {
* let prefs = new ViewHelpers.Prefs("root.path.to.branch", {
* myIntPref: ["Int", "leaf.path.to.my-int-pref"],
* myCharPref: ["Char", "leaf.path.to.my-char-pref"],
* myJsonPref: ["Json", "leaf.path.to.my-json-pref"]
* ...
* });
*
* Get/set:
* prefs.myCharPref = "foo";
* let aux = prefs.myCharPref;
*
* Observe:
* prefs.registerObserver();
* prefs.on("pref-changed", (prefName, prefValue) => {
* ...
* });
*
* @param string aPrefsRoot
* The root path to the required preferences branch.
* @param object aPrefsObject
* @param object aPrefsBlueprint
* An object containing { accessorName: [prefType, prefName] } keys.
*/
ViewHelpers.Prefs = function(aPrefsRoot = "", aPrefsObject = {}) {
this._root = aPrefsRoot;
this._cache = new Map();
ViewHelpers.Prefs = function(aPrefsRoot = "", aPrefsBlueprint = {}) {
EventEmitter.decorate(this);
for (let accessorName in aPrefsObject) {
let [prefType, prefName] = aPrefsObject[accessorName];
this.map(accessorName, prefType, prefName);
this._cache = new Map();
let self = this;
for (let [accessorName, [prefType, prefName]] of Iterator(aPrefsBlueprint)) {
this._map(accessorName, prefType, aPrefsRoot, prefName);
}
let observer = {
register: function() {
this.branch = Services.prefs.getBranch(aPrefsRoot + ".");
this.branch.addObserver("", this, false);
},
unregister: function() {
this.branch.removeObserver("", this);
},
observe: function(_, __, aPrefName) {
// If this particular pref isn't handled by the blueprint object,
// even though it's in the specified branch, ignore it.
let accessor = self._accessor(aPrefsBlueprint, aPrefName);
if (!(accessor in self)) {
return;
}
self._cache.delete(aPrefName);
self.emit("pref-changed", accessor, self[accessor]);
}
};
this.registerObserver = () => observer.register();
this.unregisterObserver = () => observer.unregister();
};
ViewHelpers.Prefs.prototype = {
@ -418,15 +451,16 @@ ViewHelpers.Prefs.prototype = {
* Helper method for getting a pref value.
*
* @param string aType
* @param string aPrefsRoot
* @param string aPrefName
* @return any
*/
_get: function(aType, aPrefName) {
_get: function(aType, aPrefsRoot, aPrefName) {
let cachedPref = this._cache.get(aPrefName);
if (cachedPref !== undefined) {
return cachedPref;
}
let value = Services.prefs["get" + aType + "Pref"](aPrefName);
let value = Services.prefs["get" + aType + "Pref"]([aPrefsRoot, aPrefName].join("."));
this._cache.set(aPrefName, value);
return value;
},
@ -435,11 +469,12 @@ ViewHelpers.Prefs.prototype = {
* Helper method for setting a pref value.
*
* @param string aType
* @param string aPrefsRoot
* @param string aPrefName
* @param any aValue
*/
_set: function(aType, aPrefName, aValue) {
Services.prefs["set" + aType + "Pref"](aPrefName, aValue);
_set: function(aType, aPrefsRoot, aPrefName, aValue) {
Services.prefs["set" + aType + "Pref"]([aPrefsRoot, aPrefName].join("."), aValue);
this._cache.set(aPrefName, aValue);
},
@ -450,26 +485,36 @@ ViewHelpers.Prefs.prototype = {
*
* @param string aAccessorName
* @param string aType
* @param string aPrefsRoot
* @param string aPrefName
* @param array aSerializer
*/
map: function(aAccessorName, aType, aPrefName, aSerializer = { in: e => e, out: e => e }) {
_map: function(aAccessorName, aType, aPrefsRoot, aPrefName, aSerializer = { in: e => e, out: e => e }) {
if (aPrefName in this) {
throw new Error(`Can't use ${aPrefName} because it's already a property.`);
}
if (aType == "Json") {
this.map(aAccessorName, "Char", aPrefName, { in: JSON.parse, out: JSON.stringify });
this._map(aAccessorName, "Char", aPrefsRoot, aPrefName, { in: JSON.parse, out: JSON.stringify });
return;
}
Object.defineProperty(this, aAccessorName, {
get: () => aSerializer.in(this._get(aType, [this._root, aPrefName].join("."))),
set: (e) => this._set(aType, [this._root, aPrefName].join("."), aSerializer.out(e))
get: () => aSerializer.in(this._get(aType, aPrefsRoot, aPrefName)),
set: (e) => this._set(aType, aPrefsRoot, aPrefName, aSerializer.out(e))
});
},
/**
* Clears all the cached preferences' values.
* Finds the accessor in this object for the provided property name,
* based on the blueprint object used in the constructor.
*/
refresh: function() {
this._cache.clear();
_accessor: function(aPrefsBlueprint, aPrefName) {
for (let [accessorName, [, prefName]] of Iterator(aPrefsBlueprint)) {
if (prefName == aPrefName) {
return accessorName;
}
}
return null;
}
};

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

@ -138,9 +138,9 @@ function finishUp() {
inspector.destroy();
inspector = null;
completer = null;
gBrowser.removeCurrentTab();
finish();
});
progress = null;
progressDiv = null;
gBrowser.removeCurrentTab();
finish();
}

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

@ -5,7 +5,6 @@ support-files =
head.js
[browser_timeline_aaa_run_first_leaktest.js]
[browser_timeline_filters.js]
[browser_timeline_overview-initial-selection-01.js]
[browser_timeline_overview-initial-selection-02.js]
[browser_timeline_overview-update.js]

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

@ -49,9 +49,7 @@ add_task(function*() {
ok(!isVisible($("#web-audio-editor-tabs")),
"InspectorView tabs are still hidden.");
let nodeSet = once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
yield nodeSet;
yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]));
ok(!isVisible($("#web-audio-editor-details-pane-empty")),
"Empty message hides even when loading node while open.");

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

@ -1,63 +1,56 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that the WebAudioInspector's Width is saved as
* a preference
*/
add_task(function*() {
let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
let { panelWin } = panel;
let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
let gVars = InspectorView._propsView;
let started = once(gFront, "start-context");
reload(target);
let [actors] = yield Promise.all([
get3(gFront, "create-node"),
waitForGraphRendered(panelWin, 3, 2)
]);
let nodeIds = actors.map(actor => actor.actorID);
ok(!InspectorView.isVisible(), "InspectorView hidden on start.");
// Open inspector pane
$("#inspector-pane-toggle").click();
yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED);
let newInspectorWidth = 500;
// Setting width to new_inspector_width
$("#web-audio-inspector").setAttribute("width", newInspectorWidth);
reload(target);
//Width should be 500 after reloading
[actors] = yield Promise.all([
get3(gFront, "create-node"),
waitForGraphRendered(panelWin, 3, 2)
]);
nodeIds = actors.map(actor => actor.actorID);
// Open inspector pane
$("#inspector-pane-toggle").click();
yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED);
let nodeSet = Promise.all([
once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET),
once(panelWin, EVENTS.UI_PROPERTIES_TAB_RENDERED),
once(panelWin, EVENTS.UI_AUTOMATION_TAB_RENDERED)
]);
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
yield nodeSet;
yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]));
// Getting the width of the audio inspector
let width = $("#web-audio-inspector").getAttribute("width");
is(width, newInspectorWidth, "WebAudioEditor's Inspector width should be saved as a preference");
yield teardown(target);
});
});

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

@ -29,12 +29,7 @@ add_task(function*() {
"InspectorView tabs view should be hidden when no node's selected.");
// Wait for the node to be set as well as the inspector to come fully into the view
let nodeSet = Promise.all([
once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET),
once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED)
]);
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
yield nodeSet;
yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]), true);
ok(InspectorView.isVisible(), "InspectorView shown once node selected.");
ok(!isVisible($("#web-audio-editor-details-pane-empty")),
@ -45,9 +40,7 @@ add_task(function*() {
is($("#web-audio-editor-tabs").selectedIndex, 0,
"default tab selected should be the parameters tab.");
nodeSet = once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
click(panelWin, findGraphNode(panelWin, nodeIds[2]));
yield nodeSet;
yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[2]));
yield teardown(target);
});

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

@ -110,6 +110,7 @@ function once(aTarget, aEventName, aUseCapture = false) {
if ((add in aTarget) && (remove in aTarget)) {
aTarget[add](aEventName, function onEvent(...aArgs) {
aTarget[remove](aEventName, onEvent, aUseCapture);
info("Got event: '" + aEventName + "' on " + aTarget + ".");
deferred.resolve(...aArgs);
}, aUseCapture);
break;
@ -352,13 +353,15 @@ function wait (n) {
/**
* Clicks a graph node based on actorID or passing in an element.
* Returns a promise that resolves once
* UI_INSPECTOR_NODE_SET is fired.
* Returns a promise that resolves once UI_INSPECTOR_NODE_SET is fired and
* the tabs have rendered, completing all RDP requests for the node.
*/
function clickGraphNode (panelWin, el, waitForToggle = false) {
let { promise, resolve } = Promise.defer();
let promises = [
once(panelWin, panelWin.EVENTS.UI_INSPECTOR_NODE_SET)
once(panelWin, panelWin.EVENTS.UI_INSPECTOR_NODE_SET),
once(panelWin, panelWin.EVENTS.UI_PROPERTIES_TAB_RENDERED),
once(panelWin, panelWin.EVENTS.UI_AUTOMATION_TAB_RENDERED)
];
if (waitForToggle) {

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

@ -39,7 +39,7 @@ let test = asyncTest(function* () {
],
});
testClickOpenNewTab(hud, results);
yield testClickOpenNewTab(hud, results);
});
function pushPrefEnv()
@ -57,18 +57,5 @@ function testClickOpenNewTab(hud, results) {
let warningNode = results[0].clickableElements[0];
ok(warningNode, "link element");
ok(warningNode.classList.contains("learn-more-link"), "link class name");
// Invoke the click event and check if a new tab would open to the correct page.
let linkOpened = false;
let oldOpenUILinkIn = window.openUILinkIn;
window.openUILinkIn = function(aLink) {
if (aLink == LEARN_MORE_URI) {
linkOpened = true;
}
}
EventUtils.synthesizeMouse(warningNode, 2, 2, {},
warningNode.ownerDocument.defaultView);
ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
window.openUILinkIn = oldOpenUILinkIn;
return simulateMessageLinkClick(warningNode, LEARN_MORE_URI);
}

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

@ -43,11 +43,11 @@ let test = asyncTest(function* () {
],
});
testClickOpenNewTab(hud, results[0]);
yield testClickOpenNewTab(hud, results[0]);
let results2 = yield mixedContentOverrideTest2(hud, browser);
testClickOpenNewTab(hud, results2[0]);
yield testClickOpenNewTab(hud, results2[0]);
});
function pushPrefEnv()
@ -118,19 +118,5 @@ function testClickOpenNewTab(hud, match) {
let warningNode = match.clickableElements[0];
ok(warningNode, "link element");
ok(warningNode.classList.contains("learn-more-link"), "link class name");
// Invoke the click event and check if a new tab would
// open to the correct page.
let linkOpened = false;
let oldOpenUILinkIn = window.openUILinkIn;
window.openUILinkIn = function(aLink) {
if (aLink == LEARN_MORE_URI) {
linkOpened = true;
}
}
EventUtils.synthesizeMouse(warningNode, 2, 2, {},
warningNode.ownerDocument.defaultView);
ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
window.openUILinkIn = oldOpenUILinkIn;
return simulateMessageLinkClick(warningNode, LEARN_MORE_URI);
}

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

@ -1,13 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
///////////////////
//
// Whitelisting this test.
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
//
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Connection closed");
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-632275-getters.html";
let getterValue = null;

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

@ -12,6 +12,7 @@
const TEST_URI = "data:text/html;charset=utf8,Web Console mixed content test";
const TEST_HTTPS_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html";
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/MixedContent";
let test = asyncTest(function* () {
Services.prefs.setBoolPref("security.mixed_content.block_display_content", false);
@ -27,45 +28,33 @@ let test = asyncTest(function* () {
Services.prefs.clearUserPref("security.mixed_content.block_active_content");
});
function testMixedContent(hud) {
let testMixedContent = Task.async(function*(hud) {
content.location = TEST_HTTPS_URI;
return waitForMessages({
let results = yield waitForMessages({
webconsole: hud,
messages: [{
text: "example.com",
category: CATEGORY_NETWORK,
severity: SEVERITY_WARNING,
}],
}).then((results) => {
let msg = [...results[0].matched][0];
ok(msg, "page load logged");
ok(msg.classList.contains("mixed-content"), ".mixed-content element");
let link = msg.querySelector(".learn-more-link");
ok(link, "mixed content link element");
is(link.textContent, "[Mixed Content]", "link text is accurate");
let oldOpenLink = hud.openLink;
let linkOpened = false;
hud.openLink = (url) => {
is(url, "https://developer.mozilla.org/docs/Security/MixedContent",
"url opened");
linkOpened = true;
};
EventUtils.synthesizeMouse(link, 2, 2, {}, link.ownerDocument.defaultView);
ok(linkOpened, "clicking the Mixed Content link opened a page");
hud.openLink = oldOpenLink;
ok(!msg.classList.contains("filtered-by-type"), "message is not filtered");
hud.setFilterState("netwarn", false);
ok(msg.classList.contains("filtered-by-type"), "message is filtered");
hud.setFilterState("netwarn", true);
});
}
let msg = [...results[0].matched][0];
ok(msg, "page load logged");
ok(msg.classList.contains("mixed-content"), ".mixed-content element");
let link = msg.querySelector(".learn-more-link");
ok(link, "mixed content link element");
is(link.textContent, "[Mixed Content]", "link text is accurate");
yield simulateMessageLinkClick(link, LEARN_MORE_URI);
ok(!msg.classList.contains("filtered-by-type"), "message is not filtered");
hud.setFilterState("netwarn", false);
ok(msg.classList.contains("filtered-by-type"), "message is filtered");
hud.setFilterState("netwarn", true);
});

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

@ -40,26 +40,12 @@ let test = asyncTest(function* () {
],
});
testClickOpenNewTab(hud, result);
yield testClickOpenNewTab(hud, result);
});
function testClickOpenNewTab(hud, [result]) {
let msg = [...result.matched][0];
let warningNode = msg.querySelector(".learn-more-link");
ok(warningNode, "learn more link");
// Invoke the click event and check if a new tab would open to the correct
// page
let linkOpened = false;
let oldOpenUILinkIn = window.openUILinkIn;
window.openUILinkIn = function(aLink) {
if (aLink == INSECURE_PASSWORDS_URI) {
linkOpened = true;
}
}
EventUtils.synthesizeMouse(warningNode, 2, 2, {},
warningNode.ownerDocument.defaultView);
ok(linkOpened, "Clicking the Insecure Passwords Warning node opens the desired page");
window.openUILinkIn = oldOpenUILinkIn;
return simulateMessageLinkClick(warningNode, INSECURE_PASSWORDS_URI);
}

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

@ -6,45 +6,30 @@ const TEST_URI = "https://example.com/browser/browser/devtools/webconsole/test/t
const HSTS_INVALID_HEADER_MSG = "The site specified an invalid Strict-Transport-Security header.";
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/HTTP_Strict_Transport_Security";
function test()
{
loadTab(TEST_URI).then(() => {
openConsole().then((hud) => {
waitForMessages({
webconsole: hud,
messages: [
{
name: "Invalid HSTS header error displayed successfully",
text: HSTS_INVALID_HEADER_MSG,
category: CATEGORY_SECURITY,
severity: SEVERITY_WARNING,
objects: true,
},
],
}).then((results) => testClickOpenNewTab(hud, results));
})
let test = asyncTest(function* () {
yield loadTab(TEST_URI);
let hud = yield openConsole();
let results = yield waitForMessages({
webconsole: hud,
messages: [
{
name: "Invalid HSTS header error displayed successfully",
text: HSTS_INVALID_HEADER_MSG,
category: CATEGORY_SECURITY,
severity: SEVERITY_WARNING,
objects: true,
},
],
});
}
yield testClickOpenNewTab(hud, results);
});
function testClickOpenNewTab(hud, results) {
let warningNode = results[0].clickableElements[0];
ok(warningNode, "link element");
ok(warningNode.classList.contains("learn-more-link"), "link class name");
// Invoke the click event and check if a new tab would
// open to the correct page.
let linkOpened = false;
let oldOpenUILinkIn = window.openUILinkIn;
window.openUILinkIn = function(aLink) {
if (aLink == LEARN_MORE_URI) {
linkOpened = true;
}
}
EventUtils.synthesizeMouse(warningNode, 2, 2, {},
warningNode.ownerDocument.defaultView);
ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
window.openUILinkIn = oldOpenUILinkIn;
finishTest();
return simulateMessageLinkClick(warningNode, LEARN_MORE_URI);
}

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

@ -1646,3 +1646,32 @@ function getSourceActor(aSources, aURL) {
let item = aSources.getItemForAttachment(a => a.source.url === aURL);
return item && item.value;
}
/**
* Verify that clicking on a link from a popup notification message tries to
* open the expected URL.
*/
function simulateMessageLinkClick(element, expectedLink) {
let deferred = promise.defer();
// Invoke the click event and check if a new tab would
// open to the correct page.
let oldOpenUILinkIn = window.openUILinkIn;
window.openUILinkIn = function(link) {
if (link == expectedLink) {
ok(true, "Clicking the message link opens the desired page");
window.openUILinkIn = oldOpenUILinkIn;
deferred.resolve();
}
};
let event = new MouseEvent("click", {
detail: 1,
button: 0,
bubbles: true,
cancelable: true
});
element.dispatchEvent(event);
return deferred.promise;
}

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

@ -153,6 +153,15 @@ function loadAddonManager() {
startupManager();
}
// Starts the addon manager without creating app info. We can't directly use
// |loadAddonManager| defined above in test_conditions.js as it would make the test fail.
function startAddonManagerOnly() {
let addonManager = Cc["@mozilla.org/addons/integration;1"]
.getService(Ci.nsIObserver)
.QueryInterface(Ci.nsITimerCallback);
addonManager.observe(null, "addons-startup", null);
}
function getExperimentAddons(previous=false) {
let deferred = Promise.defer();

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

@ -5,8 +5,14 @@
Cu.import("resource:///modules/experiments/Experiments.jsm");
Cu.import("resource://gre/modules/TelemetryPing.jsm", this);
Cu.import("resource://gre/modules/TelemetrySession.jsm", this);
XPCOMUtils.defineLazyGetter(this, "gDatareportingService",
() => Cc["@mozilla.org/datareporting/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject);
const FILE_MANIFEST = "experiments.manifest";
const SEC_IN_ONE_DAY = 24 * 60 * 60;
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
@ -45,6 +51,17 @@ function applicableFromManifestData(data, policy) {
return entry.isApplicable();
}
function initialiseTelemetry() {
// Send the needed startup notifications to the datareporting service
// to ensure that it has been initialized.
if ("@mozilla.org/datareporting/service;1" in Cc) {
gDatareportingService.observe(null, "app-startup", null);
gDatareportingService.observe(null, "profile-after-change", null);
}
return TelemetryPing.setup().then(TelemetrySession.setup);
}
function run_test() {
run_next_test();
}
@ -52,7 +69,8 @@ function run_test() {
add_task(function* test_setup() {
createAppInfo();
gProfileDir = do_get_profile();
yield TelemetrySession.setup();
startAddonManagerOnly();
yield initialiseTelemetry();
gPolicy = new Experiments.Policy();
gReporter = yield getReporter("json_payload_simple");
@ -312,5 +330,5 @@ add_task(function* test_times() {
});
add_task(function* test_shutdown() {
yield TelemetrySession.shutdown();
yield TelemetrySession.shutdown(false);
});

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

@ -64,9 +64,13 @@
- on the "+" (new tab) button for a profile when a selection is available. -->
<!ENTITY profilerUI.newtab.tooltiptext "Add new tab from selection">
<!-- LOCALIZATION NOTE (profilerUI.toolbar.filter.tooltiptext): This string
- is displayed next to the filter button-->
<!ENTITY profilerUI.options.filter.tooltiptext "Select what data to display in the timeline">
<!-- LOCALIZATION NOTE (profilerUI.options.tooltiptext): This is the tooltip
- for the options button. -->
<!ENTITY profilerUI.options.tooltiptext "Configure performance preferences.">
<!ENTITY profilerUI.options.gear.tooltiptext "Configure performance preferences.">
<!-- LOCALIZATION NOTE (profilerUI.invertTree): This is the label shown next to
- a checkbox that inverts and un-inverts the profiler's call tree. -->

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

@ -25,6 +25,10 @@
-moz-border-end-color: var(--theme-splitter-color);
}
#performance-toolbar-control-other {
-moz-padding-end: 5px;
}
#performance-toolbar-controls-detail-views > toolbarbutton {
min-width: 0;
}
@ -34,6 +38,31 @@
-moz-padding-end: 8px;
}
#filter-button {
list-style-image: url(timeline-filter.svg#filter);
min-width: 24px;
}
#filter-button[disabled] {
list-style-image: url(timeline-filter.svg#filter-disabled);
}
#filter-button[open] {
list-style-image: url(timeline-filter.svg#filter-open);
}
#performance-filter-menupopup > menuitem:before {
content: "";
display: block;
width: 8px;
height: 8px;
margin: 0 8px;
border: 1px solid;
border-radius: 1px;
background-color: var(--bullet-bg);
border-color: var(--bullet-border);
}
/* Recording Notice */
#performance-view .notice-container {

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

@ -36,6 +36,7 @@ tier_UPDATE_PACKAGING = update-packaging
tier_PRETTY_UPDATE_PACKAGING = pretty-update-packaging
tier_UPLOAD_SYMBOLS = uploadsymbols
tier_UPLOAD = upload
tier_SDK = sdk
# Automation build steps. Everything in MOZ_AUTOMATION_TIERS also gets used in
# TIERS for mach display. As such, the MOZ_AUTOMATION_TIERS are roughly sorted
@ -55,6 +56,7 @@ moz_automation_symbols = \
L10N_CHECK \
PRETTY_L10N_CHECK \
UPLOAD \
SDK \
$(NULL)
MOZ_AUTOMATION_TIERS := $(foreach sym,$(moz_automation_symbols),$(if $(filter 1,$(MOZ_AUTOMATION_$(sym))),$(tier_$(sym))))
@ -76,15 +78,17 @@ automation/upload: automation/package
automation/upload: automation/package-tests
automation/upload: automation/buildsymbols
automation/upload: automation/update-packaging
automation/upload: automation/sdk
# automation/{pretty-}package should depend on build (which is implicit due to
# the way client.mk invokes automation/build), but buildsymbols changes the
# binaries/libs, and that's what we package/test.
automation/pretty-package: automation/buildsymbols
# The installer and packager both run stage-package, and may conflict
# The installer, sdk and packager all run stage-package, and may conflict
# with each other.
automation/installer: automation/package
automation/sdk: automation/installer automation/package
# The 'pretty' versions of targets run before the regular ones to avoid
# conflicts in writing to the same files.

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

@ -17,6 +17,7 @@ mk_add_options "export MOZ_AUTOMATION_INSTALLER=${MOZ_AUTOMATION_INSTALLER-0}"
mk_add_options "export MOZ_AUTOMATION_UPDATE_PACKAGING=${MOZ_AUTOMATION_UPDATE_PACKAGING-0}"
mk_add_options "export MOZ_AUTOMATION_UPLOAD=${MOZ_AUTOMATION_UPLOAD-1}"
mk_add_options "export MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-0}"
mk_add_options "export MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-0}"
# If we are also building with MOZ_PKG_PRETTYNAMES, set the corresponding
# stages.

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

@ -252,16 +252,16 @@ SourceBuffer::Remove(double aStart, double aEnd, ErrorResult& aRv)
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (mUpdating) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (IsNaN(mMediaSource->Duration()) ||
aStart < 0 || aStart > mMediaSource->Duration() ||
aEnd <= aStart || IsNaN(aEnd)) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return;
}
if (mUpdating) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
mMediaSource->SetReadyState(MediaSourceReadyState::Open);
}

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

@ -40,5 +40,5 @@ interface SourceBuffer : EventTarget {
[Throws]
void abort();
[Throws]
void remove(double start, double end);
void remove(double start, unrestricted double end);
};

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

@ -138,14 +138,18 @@ public:
sink->Close();
RefPtr<ID2D1BitmapBrush> brush;
rt->CreateBitmapBrush(mOldSurfBitmap, D2D1::BitmapBrushProperties(), D2D1::BrushProperties(), byRef(brush));
HRESULT hr = rt->CreateBitmapBrush(mOldSurfBitmap, D2D1::BitmapBrushProperties(), D2D1::BrushProperties(), byRef(brush));
if (FAILED(hr)) {
gfxCriticalError(CriticalLog::DefaultOptions(false)) << "[D2D] CreateBitmapBrush failure " << hexa(hr);
return;
}
rt->FillGeometry(invClippedArea, brush);
}
private:
DrawTargetD2D *mDT;
DrawTargetD2D *mDT;
// If we have an operator unbound by the source, this will contain a bitmap
// with the old dest surface data.
@ -308,7 +312,12 @@ DrawTargetD2D::GetBitmapForSurface(SourceSurface *aSurface,
D2D1_BITMAP_PROPERTIES props =
D2D1::BitmapProperties(D2DPixelFormat(srcSurf->GetFormat()));
mRT->CreateBitmap(D2D1::SizeU(UINT32(sourceRect.width), UINT32(sourceRect.height)), data, stride, props, byRef(bitmap));
HRESULT hr = mRT->CreateBitmap(D2D1::SizeU(UINT32(sourceRect.width), UINT32(sourceRect.height)), data, stride, props, byRef(bitmap));
if (FAILED(hr)) {
IntSize size(sourceRect.width, sourceRect.height);
gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(size))) << "[D2D] 1CreateBitmap failure " << size << " Code: " << hexa(hr);
return nullptr;
}
// subtract the integer part leaving the fractional part
aSource.x -= (uint32_t)aSource.x;
@ -2415,14 +2424,17 @@ DrawTargetD2D::CreateBrushForPattern(const Pattern &aPattern, Float aAlpha)
}
break;
}
mRT->CreateBitmapBrush(bitmap,
HRESULT hr = mRT->CreateBitmapBrush(bitmap,
D2D1::BitmapBrushProperties(D2DExtend(pat->mExtendMode),
D2DExtend(pat->mExtendMode),
D2DFilter(pat->mFilter)),
D2D1::BrushProperties(aAlpha, D2DMatrix(mat)),
byRef(bmBrush));
if (FAILED(hr)) {
gfxCriticalError() << "[D2D] 1CreateBitmapBrush failure: " << hexa(hr);
return nullptr;
}
return bmBrush.forget();
}

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

@ -827,7 +827,7 @@ DrawTargetD2D1::Init(const IntSize &aSize, SurfaceFormat aFormat)
hr = mDC->CreateBitmap(D2DIntSize(aSize), nullptr, 0, props, (ID2D1Bitmap1**)byRef(mTempBitmap));
if (FAILED(hr)) {
gfxCriticalError() << "[D2D1.1] failed to create new TempBitmap " << aSize << " Code: " << hexa(hr);
gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize))) << "[D2D1.1] failed to create new TempBitmap " << aSize << " Code: " << hexa(hr);
return false;
}
@ -965,7 +965,13 @@ DrawTargetD2D1::FinalizeDrawing(CompositionOp aOp, const Pattern &aPattern)
}
RefPtr<ID2D1Bitmap> tmpBitmap;
mDC->CreateBitmap(D2DIntSize(mSize), D2D1::BitmapProperties(D2DPixelFormat(mFormat)), byRef(tmpBitmap));
HRESULT hr = mDC->CreateBitmap(D2DIntSize(mSize), D2D1::BitmapProperties(D2DPixelFormat(mFormat)), byRef(tmpBitmap));
if (FAILED(hr)) {
gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(mSize))) << "[D2D1.1] 5CreateBitmap failure " << mSize << " Code: " << hexa(hr);
// For now, crash in this scenario; this should happen because tmpBitmap is
// null and CopyFromBitmap call below dereferences it.
// return;
}
// This flush is important since the copy method will not know about the context drawing to the surface.
// We also need to pop all the clips to make sure any drawn content will have made it to the final bitmap.

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

@ -34,6 +34,7 @@ namespace JS {
class JS_PUBLIC_API(ProfilingFrameIterator)
{
JSRuntime *rt_;
uint32_t sampleBufferGen_;
js::Activation *activation_;
// When moving past a JitActivation, we need to save the prevJitTop
@ -68,6 +69,10 @@ class JS_PUBLIC_API(ProfilingFrameIterator)
void settle();
bool hasSampleBufferGen() const {
return sampleBufferGen_ != UINT32_MAX;
}
public:
struct RegisterState
{
@ -77,7 +82,8 @@ class JS_PUBLIC_API(ProfilingFrameIterator)
void *lr;
};
ProfilingFrameIterator(JSRuntime *rt, const RegisterState &state);
ProfilingFrameIterator(JSRuntime *rt, const RegisterState &state,
uint32_t sampleBufferGen = UINT32_MAX);
~ProfilingFrameIterator();
void operator++();
bool done() const { return !activation_; }
@ -117,6 +123,18 @@ class JS_PUBLIC_API(ProfilingFrameIterator)
bool isJit() const;
};
/**
* After each sample run, this method should be called with the latest sample
* buffer generation, and the lapCount. It will update corresponding fields on
* JSRuntime.
*
* See fields |profilerSampleBufferGen|, |profilerSampleBufferLapCount| on
* JSRuntime for documentation about what these values are used for.
*/
JS_FRIEND_API(void)
UpdateJSRuntimeProfilerSampleBufferGen(JSRuntime *runtime, uint32_t generation,
uint32_t lapCount);
} // namespace JS
#endif /* js_ProfilingFrameIterator_h */

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

@ -2203,7 +2203,7 @@ js::LookupAsmJSModuleInCache(ExclusiveContext *cx,
uint32_t srcStart = parser.pc->maybeFunction->pn_body->pn_pos.begin;
uint32_t srcBodyStart = parser.tokenStream.currentToken().pos.end;
bool strict = parser.pc->sc->strict && !parser.pc->sc->hasExplicitUseStrict();
bool strict = parser.pc->sc->strict() && !parser.pc->sc->hasExplicitUseStrict();
// usesSignalHandlers will be clobbered when deserializing
ScopedJSDeletePtr<AsmJSModule> module(

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

@ -1548,7 +1548,7 @@ class MOZ_STACK_CLASS ModuleCompiler
// "use strict" should be added to the source if we are in an implicit
// strict context, see also comment above addUseStrict in
// js::FunctionToString.
bool strict = parser_.pc->sc->strict && !parser_.pc->sc->hasExplicitUseStrict();
bool strict = parser_.pc->sc->strict() && !parser_.pc->sc->hasExplicitUseStrict();
module_ = cx_->new_<AsmJSModule>(parser_.ss, srcStart, srcBodyStart, strict,
cx_->canUseSignalHandlers());
if (!module_)

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

@ -65,6 +65,14 @@ class SplayTree
return !root;
}
T *maybeLookup(const T &v)
{
if (!root)
return nullptr;
Node *last = lookup(v);
return (C::compare(v, last->item) == 0) ? &(last->item) : nullptr;
}
bool contains(const T &v, T *res)
{
if (!root)

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

@ -247,9 +247,9 @@ UpdateDepth(ExclusiveContext *cx, BytecodeEmitter *bce, ptrdiff_t target)
static bool
CheckStrictOrSloppy(BytecodeEmitter *bce, JSOp op)
{
if (IsCheckStrictOp(op) && !bce->sc->strict)
if (IsCheckStrictOp(op) && !bce->sc->strict())
return false;
if (IsCheckSloppyOp(op) && bce->sc->strict)
if (IsCheckSloppyOp(op) && bce->sc->strict())
return false;
return true;
}
@ -1527,11 +1527,11 @@ StrictifySetNameOp(JSOp op, BytecodeEmitter *bce)
{
switch (op) {
case JSOP_SETNAME:
if (bce->sc->strict)
if (bce->sc->strict())
op = JSOP_STRICTSETNAME;
break;
case JSOP_SETGNAME:
if (bce->sc->strict)
if (bce->sc->strict())
op = JSOP_STRICTSETGNAME;
break;
default:;
@ -1664,7 +1664,7 @@ TryConvertFreeName(BytecodeEmitter *bce, ParseNode *pn)
// worth the trouble for doubly-nested eval code. So we conservatively
// approximate. If the outer eval code is strict, then this eval code will
// be: thus, don't optimize if we're compiling strict code inside an eval.
if (bce->insideEval && bce->sc->strict)
if (bce->insideEval && bce->sc->strict())
return false;
JSOp op;
@ -2233,7 +2233,7 @@ BytecodeEmitter::reportStrictModeError(ParseNode *pn, unsigned errorNumber, ...)
va_list args;
va_start(args, errorNumber);
bool result = tokenStream()->reportStrictModeErrorNumberVA(pos.begin, sc->strict,
bool result = tokenStream()->reportStrictModeErrorNumberVA(pos.begin, sc->strict(),
errorNumber, args);
va_end(args);
return result;
@ -2458,7 +2458,7 @@ EmitPropIncDec(ExclusiveContext *cx, ParseNode *pn, BytecodeEmitter *bce)
return false;
}
JSOp setOp = bce->sc->strict ? JSOP_STRICTSETPROP : JSOP_SETPROP;
JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
if (!EmitAtomOp(cx, pn->pn_kid, setOp, bce)) // N? N+1
return false;
if (post && Emit1(cx, bce, JSOP_POP) < 0) // RESULT
@ -2581,7 +2581,7 @@ EmitElemIncDec(ExclusiveContext *cx, ParseNode *pn, BytecodeEmitter *bce)
return false;
}
JSOp setOp = bce->sc->strict ? JSOP_STRICTSETELEM : JSOP_SETELEM;
JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
if (!EmitElemOpBase(cx, bce, setOp)) // N? N+1
return false;
if (post && Emit1(cx, bce, JSOP_POP) < 0) // RESULT
@ -3443,7 +3443,7 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *targ
return false;
if (Emit1(cx, bce, JSOP_SWAP) < 0)
return false;
JSOp setOp = bce->sc->strict ? JSOP_STRICTSETPROP : JSOP_SETPROP;
JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
if (!EmitAtomOp(cx, target, setOp, bce))
return false;
break;
@ -3454,7 +3454,7 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *targ
// See the comment at `case PNK_DOT:` above. This case,
// `[a[x]] = [b]`, is handled much the same way. The JSOP_SWAP
// is emitted by EmitElemOperands.
JSOp setOp = bce->sc->strict ? JSOP_STRICTSETELEM : JSOP_SETELEM;
JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
if (!EmitElemOp(cx, target, setOp, bce))
return false;
break;
@ -4215,7 +4215,7 @@ EmitAssignment(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *lhs, JSOp
break;
case PNK_DOT:
{
JSOp setOp = bce->sc->strict ? JSOP_STRICTSETPROP : JSOP_SETPROP;
JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
if (!EmitIndexOp(cx, setOp, atomIndex, bce))
return false;
break;
@ -4226,7 +4226,7 @@ EmitAssignment(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *lhs, JSOp
break;
case PNK_ELEM:
{
JSOp setOp = bce->sc->strict ? JSOP_STRICTSETELEM : JSOP_SETELEM;
JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
if (Emit1(cx, bce, setOp) < 0)
return false;
break;
@ -5408,7 +5408,7 @@ EmitFunc(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
if (outersc->isFunctionBox() && outersc->asFunctionBox()->mightAliasLocals())
funbox->setMightAliasLocals(); // inherit mightAliasLocals from parent
MOZ_ASSERT_IF(outersc->strict, funbox->strict);
MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript);
// Inherit most things (principals, version, etc) from the parent.
Rooted<JSScript*> parent(cx, bce->script);
@ -6034,7 +6034,7 @@ EmitStatement(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
// later in the script, because such statements are misleading.
const char *directive = nullptr;
if (atom == cx->names().useStrict) {
if (!bce->sc->strict)
if (!bce->sc->strictScript)
directive = js_useStrict_str;
} else if (atom == cx->names().useAsm) {
if (bce->sc->isFunctionBox()) {
@ -6076,14 +6076,14 @@ EmitDelete(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
break;
case PNK_DOT:
{
JSOp delOp = bce->sc->strict ? JSOP_STRICTDELPROP : JSOP_DELPROP;
JSOp delOp = bce->sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP;
if (!EmitPropOp(cx, pn2, delOp, bce))
return false;
break;
}
case PNK_ELEM:
{
JSOp delOp = bce->sc->strict ? JSOP_STRICTDELELEM : JSOP_DELELEM;
JSOp delOp = bce->sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM;
if (!EmitElemOp(cx, pn2, delOp, bce))
return false;
break;
@ -6559,6 +6559,103 @@ EmitConditionalExpression(ExclusiveContext *cx, BytecodeEmitter *bce, Conditiona
return SetSrcNoteOffset(cx, bce, noteIndex, 0, jmp - beq);
}
static bool
EmitPropertyList(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
MutableHandlePlainObject objp, PropListType type)
{
for (ParseNode *propdef = pn->pn_head; propdef; propdef = propdef->pn_next) {
if (!UpdateSourceCoordNotes(cx, bce, propdef->pn_pos.begin))
return false;
// Handle __proto__: v specially because *only* this form, and no other
// involving "__proto__", performs [[Prototype]] mutation.
if (propdef->isKind(PNK_MUTATEPROTO)) {
MOZ_ASSERT(type == ObjectLiteral);
if (!EmitTree(cx, bce, propdef->pn_kid))
return false;
objp.set(nullptr);
if (!Emit1(cx, bce, JSOP_MUTATEPROTO))
return false;
continue;
}
/* Emit an index for t[2] for later consumption by JSOP_INITELEM. */
ParseNode *key = propdef->pn_left;
bool isIndex = false;
if (key->isKind(PNK_NUMBER)) {
if (!EmitNumberOp(cx, key->pn_dval, bce))
return false;
isIndex = true;
} else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) {
// EmitClass took care of constructor already.
if (type == ClassBody && key->pn_atom == cx->names().constructor)
continue;
// The parser already checked for atoms representing indexes and
// used PNK_NUMBER instead, but also watch for ids which TI treats
// as indexes for simpliciation of downstream analysis.
jsid id = NameToId(key->pn_atom->asPropertyName());
if (id != IdToTypeId(id)) {
if (!EmitTree(cx, bce, key))
return false;
isIndex = true;
}
} else {
MOZ_ASSERT(key->isKind(PNK_COMPUTED_NAME));
if (!EmitTree(cx, bce, key->pn_kid))
return false;
isIndex = true;
}
/* Emit code for the property initializer. */
if (!EmitTree(cx, bce, propdef->pn_right))
return false;
JSOp op = propdef->getOp();
MOZ_ASSERT(op == JSOP_INITPROP ||
op == JSOP_INITPROP_GETTER ||
op == JSOP_INITPROP_SETTER);
if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER)
objp.set(nullptr);
if (isIndex) {
objp.set(nullptr);
switch (op) {
case JSOP_INITPROP: op = JSOP_INITELEM; break;
case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break;
case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break;
default: MOZ_CRASH("Invalid op");
}
if (Emit1(cx, bce, op) < 0)
return false;
} else {
MOZ_ASSERT(key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING));
jsatomid index;
if (!bce->makeAtomIndex(key->pn_atom, &index))
return false;
if (objp) {
MOZ_ASSERT(!objp->inDictionaryMode());
Rooted<jsid> id(cx, AtomToId(key->pn_atom));
RootedValue undefinedValue(cx, UndefinedValue());
if (!NativeDefineProperty(cx, objp, id, undefinedValue, nullptr, nullptr,
JSPROP_ENUMERATE))
{
return false;
}
if (objp->inDictionaryMode())
objp.set(nullptr);
}
if (!EmitIndex32(cx, op, index, bce))
return false;
}
}
return true;
}
/*
* Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
* the comment on EmitSwitch.
@ -6590,91 +6687,8 @@ EmitObject(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
return false;
}
for (ParseNode *propdef = pn->pn_head; propdef; propdef = propdef->pn_next) {
if (!UpdateSourceCoordNotes(cx, bce, propdef->pn_pos.begin))
return false;
// Handle __proto__: v specially because *only* this form, and no other
// involving "__proto__", performs [[Prototype]] mutation.
if (propdef->isKind(PNK_MUTATEPROTO)) {
if (!EmitTree(cx, bce, propdef->pn_kid))
return false;
obj = nullptr;
if (!Emit1(cx, bce, JSOP_MUTATEPROTO))
return false;
continue;
}
/* Emit an index for t[2] for later consumption by JSOP_INITELEM. */
ParseNode *key = propdef->pn_left;
bool isIndex = false;
if (key->isKind(PNK_NUMBER)) {
if (!EmitNumberOp(cx, key->pn_dval, bce))
return false;
isIndex = true;
} else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) {
// The parser already checked for atoms representing indexes and
// used PNK_NUMBER instead, but also watch for ids which TI treats
// as indexes for simpliciation of downstream analysis.
jsid id = NameToId(key->pn_atom->asPropertyName());
if (id != IdToTypeId(id)) {
if (!EmitTree(cx, bce, key))
return false;
isIndex = true;
}
} else {
MOZ_ASSERT(key->isKind(PNK_COMPUTED_NAME));
if (!EmitTree(cx, bce, key->pn_kid))
return false;
isIndex = true;
}
/* Emit code for the property initializer. */
if (!EmitTree(cx, bce, propdef->pn_right))
return false;
JSOp op = propdef->getOp();
MOZ_ASSERT(op == JSOP_INITPROP ||
op == JSOP_INITPROP_GETTER ||
op == JSOP_INITPROP_SETTER);
if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER)
obj = nullptr;
if (isIndex) {
obj = nullptr;
switch (op) {
case JSOP_INITPROP: op = JSOP_INITELEM; break;
case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break;
case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break;
default: MOZ_CRASH("Invalid op");
}
if (Emit1(cx, bce, op) < 0)
return false;
} else {
MOZ_ASSERT(key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING));
jsatomid index;
if (!bce->makeAtomIndex(key->pn_atom, &index))
return false;
if (obj) {
MOZ_ASSERT(!obj->inDictionaryMode());
Rooted<jsid> id(cx, AtomToId(key->pn_atom));
RootedValue undefinedValue(cx, UndefinedValue());
if (!NativeDefineProperty(cx, obj, id, undefinedValue, nullptr, nullptr,
JSPROP_ENUMERATE))
{
return false;
}
if (obj->inDictionaryMode())
obj = nullptr;
}
if (!EmitIndex32(cx, op, index, bce))
return false;
}
}
if (!EmitPropertyList(cx, bce, pn, &obj, ObjectLiteral))
return false;
if (obj) {
/*
@ -6865,6 +6879,120 @@ EmitDefaults(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
return true;
}
static bool
EmitLexicalInitialization(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
JSOp globalDefOp)
{
/*
* This function is significantly more complicated than it needs to be.
* In fact, it shouldn't exist at all. This should all be a
* JSOP_INITLEXIAL. Unfortunately, toplevel lexicals are broken, and
* are emitted as vars :(. As such, we have to do these ministrations to
* to make sure that all works properly.
*/
MOZ_ASSERT(pn->isKind(PNK_NAME));
if (!BindNameToSlot(cx, bce, pn))
return false;
jsatomid atomIndex;
if (!MaybeEmitVarDecl(cx, bce, globalDefOp, pn, &atomIndex))
return false;
if (pn->getOp() != JSOP_INITLEXICAL) {
bool global = js_CodeSpec[pn->getOp()].format & JOF_GNAME;
if (!EmitIndex32(cx, global ? JSOP_BINDGNAME : JSOP_BINDNAME, atomIndex, bce))
return false;
if (Emit1(cx, bce, JSOP_SWAP) < 0)
return false;
}
if (!pn->pn_cookie.isFree()) {
if (!EmitVarOp(cx, pn, pn->getOp(), bce))
return false;
} else {
if (!EmitIndexOp(cx, pn->getOp(), atomIndex, bce))
return false;
}
return true;
}
// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15
// (BindingClassDeclarationEvaluation).
static bool
EmitClass(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
ClassNode &classNode = pn->as<ClassNode>();
ClassNames *names = classNode.names();
MOZ_ASSERT(!classNode.heritage(), "For now, no heritage expressions");
LexicalScopeNode *innerBlock = classNode.scope();
ParseNode *classMethods = innerBlock->pn_expr;
MOZ_ASSERT(classMethods->isKind(PNK_CLASSMETHODLIST));
ParseNode *constructor = nullptr;
for (ParseNode *mn = classMethods->pn_head; mn; mn = mn->pn_next) {
ClassMethod &method = mn->as<ClassMethod>();
ParseNode &methodName = method.name();
if (methodName.isKind(PNK_OBJECT_PROPERTY_NAME) &&
methodName.pn_atom == cx->names().constructor)
{
constructor = &method.method();
break;
}
}
MOZ_ASSERT(constructor, "For now, no default constructors");
bool savedStrictness = bce->sc->setLocalStrictMode(true);
StmtInfoBCE stmtInfo(cx);
if (!EnterBlockScope(cx, bce, &stmtInfo, innerBlock->pn_objbox, JSOP_UNINITIALIZED))
return false;
if (!EmitFunc(cx, bce, constructor))
return false;
if (!EmitNewInit(cx, bce, JSProto_Object))
return false;
if (Emit1(cx, bce, JSOP_DUP2) < 0)
return false;
if (!EmitAtomOp(cx, cx->names().prototype, JSOP_INITLOCKEDPROP, bce))
return false;
if (!EmitAtomOp(cx, cx->names().constructor, JSOP_INITHIDDENPROP, bce))
return false;
RootedPlainObject obj(cx);
if (!EmitPropertyList(cx, bce, classMethods, &obj, ClassBody))
return false;
if (Emit1(cx, bce, JSOP_POP) < 0)
return false;
// That DEFCONST is never gonna be used, but use it here for logical consistency.
ParseNode *innerName = names->innerBinding();
if (!EmitLexicalInitialization(cx, bce, innerName, JSOP_DEFCONST))
return false;
if (!LeaveNestedScope(cx, bce, &stmtInfo))
return false;
ParseNode *outerName = names->outerBinding();
if (!EmitLexicalInitialization(cx, bce, outerName, JSOP_DEFVAR))
return false;
if (Emit1(cx, bce, JSOP_POP) < 0)
return false;
MOZ_ALWAYS_TRUE(bce->sc->setLocalStrictMode(savedStrictness));
return true;
}
bool
frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
@ -7312,6 +7440,10 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
MOZ_ASSERT(pn->getArity() == PN_NULLARY);
break;
case PNK_CLASS:
ok = EmitClass(cx, bce, pn);
break;
default:
MOZ_ASSERT(0);
}

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

@ -80,6 +80,13 @@ ContainsHoistedDeclaration(ExclusiveContext *cx, ParseNode *node, bool *result)
*result = false;
return true;
// Similarly to the lexical declarations above, classes cannot add hoisted
// declarations
case PNK_CLASS:
MOZ_ASSERT(node->isArity(PN_TERNARY));
*result = false;
return true;
// ContainsHoistedDeclaration is only called on nested nodes, so any
// instance of this can't be function statements at body level. In
// SpiderMonkey, a binding induced by a function statement is added when
@ -405,6 +412,9 @@ ContainsHoistedDeclaration(ExclusiveContext *cx, ParseNode *node, bool *result)
case PNK_FORIN:
case PNK_FOROF:
case PNK_FORHEAD:
case PNK_CLASSMETHOD:
case PNK_CLASSMETHODLIST:
case PNK_CLASSNAMES:
MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on "
"some parent node without recurring to test this node");

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

@ -281,6 +281,16 @@ class FullParseHandler
return literal;
}
ParseNode *newClass(ParseNode *name, ParseNode *heritage, ParseNode *methodBlock) {
return new_<ClassNode>(name, heritage, methodBlock);
}
ParseNode *newClassMethodList(uint32_t begin) {
return new_<ListNode>(PNK_CLASSMETHODLIST, TokenPos(begin, begin + 1));
}
ParseNode *newClassNames(ParseNode *outer, ParseNode *inner, const TokenPos &pos) {
return new_<ClassNames>(outer, inner, pos);
}
bool addPrototypeMutation(ParseNode *literal, uint32_t begin, ParseNode *expr) {
// Object literals with mutated [[Prototype]] are non-constant so that
// singleton objects will have Object.prototype as their [[Prototype]].
@ -323,7 +333,7 @@ class FullParseHandler
return true;
}
bool addMethodDefinition(ParseNode *literal, ParseNode *key, ParseNode *fn, JSOp op)
bool addObjectMethodDefinition(ParseNode *literal, ParseNode *key, ParseNode *fn, JSOp op)
{
MOZ_ASSERT(literal->isArity(PN_LIST));
MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
@ -339,6 +349,22 @@ class FullParseHandler
return true;
}
bool addClassMethodDefinition(ParseNode *methodList, ParseNode *key, ParseNode *fn, JSOp op)
{
MOZ_ASSERT(methodList->isKind(PNK_CLASSMETHODLIST));
MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
key->isKind(PNK_STRING) ||
key->isKind(PNK_COMPUTED_NAME));
// For now, there's no such thing as static methods.
ParseNode *classMethod = new_<ClassMethod>(key, fn, op, false);
if (!classMethod)
return false;
methodList->append(classMethod);
return true;
}
ParseNode *newYieldExpression(uint32_t begin, ParseNode *value, ParseNode *gen,
JSOp op = JSOP_YIELD) {
TokenPos pos(begin, value ? value->pn_pos.end : begin + 1);

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

@ -268,6 +268,8 @@ PushNodeChildren(ParseNode *pn, NodeStack *stack)
case PNK_WHILE:
case PNK_SWITCH:
case PNK_LETBLOCK:
case PNK_CLASSNAMES:
case PNK_CLASSMETHOD:
case PNK_FOR: {
MOZ_ASSERT(pn->isArity(PN_BINARY));
stack->push(pn->pn_left);
@ -381,6 +383,16 @@ PushNodeChildren(ParseNode *pn, NodeStack *stack)
return PushResult::Recyclable;
}
// classes might have an optinal node for the heritage
case PNK_CLASS: {
MOZ_ASSERT(pn->isArity(PN_TERNARY));
stack->push(pn->pn_kid1);
if (pn->pn_kid2)
stack->push(pn->pn_kid2);
stack->push(pn->pn_kid3);
return PushResult::Recyclable;
}
// if-statement nodes have condition and consequent children and a
// possibly-null alternative.
case PNK_IF: {
@ -461,6 +473,7 @@ PushNodeChildren(ParseNode *pn, NodeStack *stack)
case PNK_EXPORT_SPEC_LIST:
case PNK_SEQ:
case PNK_ARGSBODY:
case PNK_CLASSMETHODLIST:
return PushListNodeChildren(pn, stack);
// Array comprehension nodes are lists with a single child -- PNK_FOR for
@ -630,7 +643,7 @@ Parser<FullParseHandler>::cloneParseTree(ParseNode *opn)
case PN_CODE:
NULLCHECK(pn->pn_funbox = newFunctionBox(pn, opn->pn_funbox->function(), pc,
Directives(/* strict = */ opn->pn_funbox->strict),
Directives(/* strict = */ opn->pn_funbox->strict()),
opn->pn_funbox->generatorKind()));
NULLCHECK(pn->pn_body = cloneParseTree(opn->pn_body));
pn->pn_cookie = opn->pn_cookie;

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

@ -149,6 +149,10 @@ class UpvarCookie
F(ARGSBODY) \
F(SPREAD) \
F(MUTATEPROTO) \
F(CLASS) \
F(CLASSMETHOD) \
F(CLASSMETHODLIST) \
F(CLASSNAMES) \
\
/* Unary operators. */ \
F(TYPEOF) \
@ -571,6 +575,7 @@ class ParseNode
union {
unsigned iflags; /* JSITER_* flags for PNK_FOR node */
ObjectBox *objbox; /* Only for PN_BINARY_OBJ */
bool isStatic; /* Only for PNK_CLASSMETHOD */
};
} binary;
struct { /* one kid if unary */
@ -1102,6 +1107,10 @@ struct LexicalScopeNode : public ParseNode
pn_expr = blockNode;
pn_blockid = blockNode->pn_blockid;
}
static bool test(const ParseNode &node) {
return node.isKind(PNK_LEXICALSCOPE);
}
};
class LabeledStatement : public ParseNode
@ -1319,6 +1328,91 @@ struct CallSiteNode : public ListNode {
}
};
struct ClassMethod : public BinaryNode {
/*
* Method defintions often keep a name and function body that overlap,
* so explicitly define the beginning and end here.
*/
ClassMethod(ParseNode *name, ParseNode *body, JSOp op, bool isStatic)
: BinaryNode(PNK_CLASSMETHOD, op, TokenPos(name->pn_pos.begin, body->pn_pos.end), name, body)
{
pn_u.binary.isStatic = isStatic;
}
static bool test(const ParseNode &node) {
bool match = node.isKind(PNK_CLASSMETHOD);
MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
return match;
}
ParseNode &name() const {
return *pn_u.binary.left;
}
ParseNode &method() const {
return *pn_u.binary.right;
}
bool isStatic() const {
return pn_u.binary.isStatic;
}
};
struct ClassNames : public BinaryNode {
ClassNames(ParseNode *outerBinding, ParseNode *innerBinding, const TokenPos &pos)
: BinaryNode(PNK_CLASSNAMES, JSOP_NOP, pos, outerBinding, innerBinding)
{
MOZ_ASSERT(outerBinding->isKind(PNK_NAME));
MOZ_ASSERT(innerBinding->isKind(PNK_NAME));
MOZ_ASSERT(innerBinding->pn_atom == outerBinding->pn_atom);
}
static bool test(const ParseNode &node) {
bool match = node.isKind(PNK_CLASSNAMES);
MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
return match;
}
/*
* Classes require two definitions: The first "outer" binding binds the
* class into the scope in which it was declared. the outer binding is a
* mutable lexial binding. The second "inner" binding binds the class by
* name inside a block in which the methods are evaulated. It is immutable,
* giving the methods access to the static members of the class even if
* the outer binding has been overwritten.
*/
ParseNode *outerBinding() const {
return pn_u.binary.left;
}
ParseNode *innerBinding() const {
return pn_u.binary.right;
}
};
struct ClassNode : public TernaryNode {
ClassNode(ParseNode *names, ParseNode *heritage, ParseNode *methodBlock)
: TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, methodBlock)
{
MOZ_ASSERT(names->is<ClassNames>());
MOZ_ASSERT(methodBlock->is<LexicalScopeNode>());
}
static bool test(const ParseNode &node) {
bool match = node.isKind(PNK_CLASS);
MOZ_ASSERT_IF(match, node.isArity(PN_TERNARY));
return match;
}
ClassNames *names() const {
return &pn_kid1->as<ClassNames>();
}
ParseNode *heritage() const {
return pn_kid2;
}
LexicalScopeNode *scope() const {
return &pn_kid3->as<LexicalScopeNode>();
}
};
#ifdef DEBUG
void DumpParseTree(ParseNode *pn, int indent = 0);
#endif
@ -1553,7 +1647,7 @@ enum ParseReportKind
ParseStrictError
};
enum FunctionSyntaxKind { Expression, Statement, Arrow, Method };
enum FunctionSyntaxKind { Expression, Statement, Arrow, Method, Lazy };
static inline ParseNode *
FunctionArgsList(ParseNode *fn, unsigned *numFormals)

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

@ -754,7 +754,7 @@ Parser<ParseHandler>::reportBadReturn(Node pn, ParseReportKind kind,
} else {
errnum = anonerrnum;
}
return report(kind, pc->sc->strict, pn, errnum, name.ptr());
return report(kind, pc->sc->strict(), pn, errnum, name.ptr());
}
/*
@ -777,7 +777,7 @@ Parser<ParseHandler>::checkStrictAssignment(Node lhs)
if (!AtomToPrintableString(context, atom, &name))
return false;
if (!report(ParseStrictError, pc->sc->strict, lhs, JSMSG_BAD_STRICT_ASSIGN, name.ptr()))
if (!report(ParseStrictError, pc->sc->strict(), lhs, JSMSG_BAD_STRICT_ASSIGN, name.ptr()))
return false;
}
return true;
@ -800,7 +800,7 @@ Parser<ParseHandler>::checkStrictBinding(PropertyName *name, Node pn)
JSAutoByteString bytes;
if (!AtomToPrintableString(context, name, &bytes))
return false;
return report(ParseStrictError, pc->sc->strict, pn,
return report(ParseStrictError, pc->sc->strict(), pn,
JSMSG_BAD_BINDING, bytes.ptr());
}
@ -1505,7 +1505,7 @@ Parser<ParseHandler>::defineArg(Node funcpn, HandlePropertyName name,
JSAutoByteString bytes;
if (!AtomToPrintableString(context, name, &bytes))
return false;
if (!report(ParseStrictError, pc->sc->strict, pn,
if (!report(ParseStrictError, pc->sc->strict(), pn,
JSMSG_DUPLICATE_FORMAL, bytes.ptr()))
{
return false;
@ -1892,7 +1892,7 @@ Parser<FullParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
* statements (e.g., functions in an "if" or "while" block) are
* dynamically bound when control flow reaches the statement.
*/
MOZ_ASSERT(!pc->sc->strict);
MOZ_ASSERT(!pc->sc->strict());
MOZ_ASSERT(pn->pn_cookie.isFree());
if (pc->sc->isFunctionBox()) {
FunctionBox *funbox = pc->sc->asFunctionBox();
@ -2278,7 +2278,7 @@ Parser<SyntaxParseHandler>::finishFunctionDefinition(Node pn, FunctionBox *funbo
for (size_t i = 0; i < numInnerFunctions; i++)
innerFunctions[i].init(pc->innerFunctions[i]);
if (pc->sc->strict)
if (pc->sc->strict())
lazy->setStrict();
lazy->setGeneratorKind(funbox->generatorKind());
if (funbox->usesArguments && funbox->usesApply && funbox->usesThis)
@ -2470,7 +2470,7 @@ Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, unsigned st
if (!funpc.init(tokenStream))
return null();
if (!functionArgsAndBodyGeneric(pn, fun, Normal, Statement)) {
if (!functionArgsAndBodyGeneric(pn, fun, Normal, Lazy)) {
MOZ_ASSERT(directives == newDirectives);
return null();
}
@ -2555,8 +2555,11 @@ Parser<ParseHandler>::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, Fu
if (!body)
return false;
if (fun->name() && !checkStrictBinding(fun->name(), pn))
if (kind != Method && kind != Lazy &&
fun->name() && !checkStrictBinding(fun->name(), pn))
{
return false;
}
if (bodyType == StatementListBody) {
bool matched;
@ -2574,7 +2577,7 @@ Parser<ParseHandler>::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, Fu
if (tokenStream.hadError())
return false;
funbox->bufEnd = pos().end;
if (kind == Statement && !MatchOrInsertSemicolon(tokenStream))
if ((kind == Statement || kind == Lazy) && !MatchOrInsertSemicolon(tokenStream))
return false;
}
@ -2587,7 +2590,7 @@ Parser<ParseHandler>::checkYieldNameValidity()
{
// In star generators and in JS >= 1.7, yield is a keyword. Otherwise in
// strict mode, yield is a future reserved word.
if (pc->isStarGenerator() || versionNumber() >= JSVERSION_1_7 || pc->sc->strict) {
if (pc->isStarGenerator() || versionNumber() >= JSVERSION_1_7 || pc->sc->strict()) {
report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield");
return false;
}
@ -2626,7 +2629,7 @@ Parser<ParseHandler>::functionStmt()
/* We forbid function statements in strict mode code. */
if (!pc->atBodyLevel() && pc->sc->needStrictChecks() &&
!report(ParseStrictError, pc->sc->strict, null(), JSMSG_STRICT_FUNCTION_STATEMENT))
!report(ParseStrictError, pc->sc->strict(), null(), JSMSG_STRICT_FUNCTION_STATEMENT))
return null();
return functionDef(name, Normal, Statement, generatorKind);
@ -2779,7 +2782,7 @@ Parser<ParseHandler>::maybeParseDirective(Node list, Node pn, bool *cont)
// We're going to be in strict mode. Note that this scope explicitly
// had "use strict";
pc->sc->setExplicitUseStrict();
if (!pc->sc->strict) {
if (!pc->sc->strict()) {
if (pc->sc->isFunctionBox()) {
// Request that this function be reparsed as strict.
pc->newDirectives->setStrict();
@ -2792,7 +2795,7 @@ Parser<ParseHandler>::maybeParseDirective(Node list, Node pn, bool *cont)
report(ParseError, false, null(), JSMSG_DEPRECATED_OCTAL);
return false;
}
pc->sc->strict = true;
pc->sc->strictScript = true;
}
}
} else if (directive == context->names().useAsm) {
@ -3263,7 +3266,7 @@ Parser<FullParseHandler>::makeSetCall(ParseNode *pn, unsigned msg)
pn->isOp(JSOP_SPREADEVAL) || pn->isOp(JSOP_STRICTSPREADEVAL) ||
pn->isOp(JSOP_FUNCALL) || pn->isOp(JSOP_FUNAPPLY));
if (!report(ParseStrictError, pc->sc->strict, pn, msg))
if (!report(ParseStrictError, pc->sc->strict(), pn, msg))
return false;
handler.markAsSetCall(pn);
return true;
@ -3322,7 +3325,7 @@ Parser<ParseHandler>::noteNameUse(HandlePropertyName name, Node pn)
template <>
bool
Parser<FullParseHandler>::bindDestructuringVar(BindData<FullParseHandler> *data, ParseNode *pn)
Parser<FullParseHandler>::bindInitialized(BindData<FullParseHandler> *data, ParseNode *pn)
{
MOZ_ASSERT(pn->isKind(PNK_NAME));
@ -3382,7 +3385,7 @@ Parser<FullParseHandler>::checkDestructuringObject(BindData<FullParseHandler> *d
report(ParseError, false, expr, JSMSG_NO_VARIABLE_NAME);
return false;
}
ok = bindDestructuringVar(data, expr);
ok = bindInitialized(data, expr);
} else {
ok = checkAndMarkAsAssignmentLhs(expr, KeyedDestructuringAssignment);
}
@ -3430,7 +3433,7 @@ Parser<FullParseHandler>::checkDestructuringArray(BindData<FullParseHandler> *da
report(ParseError, false, target, JSMSG_NO_VARIABLE_NAME);
return false;
}
ok = bindDestructuringVar(data, target);
ok = bindInitialized(data, target);
} else {
ok = checkAndMarkAsAssignmentLhs(target, KeyedDestructuringAssignment);
}
@ -3660,7 +3663,7 @@ Parser<ParseHandler>::deprecatedLetBlockOrExpression(LetContext letContext)
*
* See bug 569464.
*/
if (!reportWithOffset(ParseStrictError, pc->sc->strict, begin,
if (!reportWithOffset(ParseStrictError, pc->sc->strict(), begin,
JSMSG_STRICT_CODE_LET_EXPR_STMT))
{
return null();
@ -3684,7 +3687,7 @@ Parser<ParseHandler>::deprecatedLetBlockOrExpression(LetContext letContext)
MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_LET);
addTelemetry(JSCompartment::DeprecatedLetBlock);
if (!report(ParseWarning, pc->sc->strict, expr, JSMSG_DEPRECATED_LET_BLOCK))
if (!report(ParseWarning, pc->sc->strict(), expr, JSMSG_DEPRECATED_LET_BLOCK))
return null();
} else {
MOZ_ASSERT(letContext == LetExpression);
@ -3693,7 +3696,7 @@ Parser<ParseHandler>::deprecatedLetBlockOrExpression(LetContext letContext)
return null();
addTelemetry(JSCompartment::DeprecatedLetExpression);
if (!report(ParseWarning, pc->sc->strict, expr, JSMSG_DEPRECATED_LET_EXPRESSION))
if (!report(ParseWarning, pc->sc->strict(), expr, JSMSG_DEPRECATED_LET_EXPRESSION))
return null();
}
handler.setLexicalScopeBody(block, expr);
@ -3950,136 +3953,169 @@ Parser<ParseHandler>::variables(ParseNodeKind kind, bool *psimple,
return pn;
}
template <>
bool
Parser<FullParseHandler>::checkAndPrepareLexical(bool isConst, const TokenPos &errorPos)
{
/*
* This is a lexical declaration. We must be directly under a block per the
* proposed ES4 specs, but not an implicit block created due to
* 'for (let ...)'. If we pass this error test, make the enclosing
* StmtInfoPC be our scope. Further let declarations in this block will
* find this scope statement and use the same block object.
*
* If we are the first let declaration in this block (i.e., when the
* enclosing maybe-scope StmtInfoPC isn't yet a scope statement) then
* we also need to set pc->blockNode to be our PNK_LEXICALSCOPE.
*/
StmtInfoPC *stmt = pc->topStmt;
if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) {
reportWithOffset(ParseError, false, errorPos.begin, JSMSG_LEXICAL_DECL_NOT_IN_BLOCK,
isConst ? "const" : "lexical");
return false;
}
if (stmt && stmt->isBlockScope) {
MOZ_ASSERT(pc->staticScope == stmt->staticScope);
} else {
if (pc->atBodyLevel()) {
/*
* When bug 589199 is fixed, let variables will be stored in
* the slots of a new scope chain object, encountered just
* before the global object in the overall chain. This extra
* object is present in the scope chain for all code in that
* global, including self-hosted code. But self-hosted code
* must be usable against *any* global object, including ones
* with other let variables -- variables possibly placed in
* conflicting slots. Forbid top-level let declarations to
* prevent such conflicts from ever occurring.
*/
bool isGlobal = !pc->sc->isFunctionBox() && stmt == pc->topScopeStmt;
if (options().selfHostingMode && isGlobal) {
report(ParseError, false, null(), JSMSG_SELFHOSTED_TOP_LEVEL_LEXICAL,
isConst ? "'const'" : "'let'");
return false;
}
return true;
}
/*
* Some obvious assertions here, but they may help clarify the
* situation. This stmt is not yet a scope, so it must not be a
* catch block (catch is a lexical scope by definition).
*/
MOZ_ASSERT(!stmt->isBlockScope);
MOZ_ASSERT(stmt != pc->topScopeStmt);
MOZ_ASSERT(stmt->type == STMT_BLOCK ||
stmt->type == STMT_SWITCH ||
stmt->type == STMT_TRY ||
stmt->type == STMT_FINALLY);
MOZ_ASSERT(!stmt->downScope);
/* Convert the block statement into a scope statement. */
StaticBlockObject *blockObj = StaticBlockObject::create(context);
if (!blockObj)
return false;
ObjectBox *blockbox = newObjectBox(blockObj);
if (!blockbox)
return false;
/*
* Insert stmt on the pc->topScopeStmt/stmtInfo.downScope linked
* list stack, if it isn't already there. If it is there, but it
* lacks the SIF_SCOPE flag, it must be a try, catch, or finally
* block.
*/
stmt->isBlockScope = stmt->isNestedScope = true;
stmt->downScope = pc->topScopeStmt;
pc->topScopeStmt = stmt;
blockObj->initEnclosingNestedScopeFromParser(pc->staticScope);
pc->staticScope = blockObj;
stmt->staticScope = blockObj;
#ifdef DEBUG
ParseNode *tmp = pc->blockNode;
MOZ_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE));
#endif
/* Create a new lexical scope node for these statements. */
ParseNode *pn1 = handler.new_<LexicalScopeNode>(blockbox, pc->blockNode);
if (!pn1)
return false;;
pc->blockNode = pn1;
}
return true;
}
static StaticBlockObject *
CurrentLexicalStaticBlock(ParseContext<FullParseHandler> *pc)
{
return pc->atBodyLevel() ? nullptr :
&pc->staticScope->as<StaticBlockObject>();
}
template <>
ParseNode *
Parser<FullParseHandler>::makeInitializedLexicalBinding(HandlePropertyName name, bool isConst,
const TokenPos &pos)
{
// Handle the silliness of global and body level lexical decls.
BindData<FullParseHandler> data(context);
if (pc->atGlobalLevel()) {
data.initVarOrGlobalConst(isConst ? JSOP_DEFCONST : JSOP_DEFVAR);
} else {
if (!checkAndPrepareLexical(isConst, pos))
return null();
data.initLexical(HoistVars, CurrentLexicalStaticBlock(pc), JSMSG_TOO_MANY_LOCALS, isConst);
}
ParseNode *dn = newBindingNode(name, pc->atGlobalLevel());
if (!dn)
return null();
handler.setPosition(dn, pos);
if (!bindInitialized(&data, dn))
return null();
return dn;
}
template <>
ParseNode *
Parser<FullParseHandler>::lexicalDeclaration(bool isConst)
{
handler.disableSyntaxParser();
ParseNode *pn;
if (!checkAndPrepareLexical(isConst, pos()))
return null();
do {
/*
* This is a let declaration. We must be directly under a block per the
* proposed ES4 specs, but not an implicit block created due to
* 'for (let ...)'. If we pass this error test, make the enclosing
* StmtInfoPC be our scope. Further let declarations in this block will
* find this scope statement and use the same block object.
*
* If we are the first let declaration in this block (i.e., when the
* enclosing maybe-scope StmtInfoPC isn't yet a scope statement) then
* we also need to set pc->blockNode to be our PNK_LEXICALSCOPE.
*/
StmtInfoPC *stmt = pc->topStmt;
if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) {
report(ParseError, false, null(), JSMSG_LEXICAL_DECL_NOT_IN_BLOCK,
isConst ? "const" : "let");
return null();
}
if (stmt && stmt->isBlockScope) {
MOZ_ASSERT(pc->staticScope == stmt->staticScope);
} else {
if (pc->atBodyLevel()) {
/*
* When bug 589199 is fixed, let variables will be stored in
* the slots of a new scope chain object, encountered just
* before the global object in the overall chain. This extra
* object is present in the scope chain for all code in that
* global, including self-hosted code. But self-hosted code
* must be usable against *any* global object, including ones
* with other let variables -- variables possibly placed in
* conflicting slots. Forbid top-level let declarations to
* prevent such conflicts from ever occurring.
*/
bool isGlobal = !pc->sc->isFunctionBox() && stmt == pc->topScopeStmt;
if (options().selfHostingMode && isGlobal) {
report(ParseError, false, null(), JSMSG_SELFHOSTED_TOP_LEVEL_LEXICAL,
isConst ? "'const'" : "'let'");
return null();
}
/*
* Parse body-level lets without a new block object. ES6 specs
* that an execution environment's initial lexical environment
* is the VariableEnvironment, i.e., body-level lets are in
* the same environment record as vars.
*
* However, they cannot be parsed exactly as vars, as ES6
* requires that uninitialized lets throw ReferenceError on use.
*
* See 8.1.1.1.6 and the note in 13.2.1.
*
* FIXME global-level lets are still considered vars until
* other bugs are fixed.
*/
ParseNodeKind kind = PNK_LET;
if (isGlobal)
kind = isConst ? PNK_GLOBALCONST : PNK_VAR;
else if (isConst)
kind = PNK_CONST;
pn = variables(kind);
if (!pn)
return null();
pn->pn_xflags |= PNX_POPVAR;
break;
}
/*
* Some obvious assertions here, but they may help clarify the
* situation. This stmt is not yet a scope, so it must not be a
* catch block (catch is a lexical scope by definition).
*/
MOZ_ASSERT(!stmt->isBlockScope);
MOZ_ASSERT(stmt != pc->topScopeStmt);
MOZ_ASSERT(stmt->type == STMT_BLOCK ||
stmt->type == STMT_SWITCH ||
stmt->type == STMT_TRY ||
stmt->type == STMT_FINALLY);
MOZ_ASSERT(!stmt->downScope);
/* Convert the block statement into a scope statement. */
StaticBlockObject *blockObj = StaticBlockObject::create(context);
if (!blockObj)
return null();
ObjectBox *blockbox = newObjectBox(blockObj);
if (!blockbox)
return null();
/*
* Insert stmt on the pc->topScopeStmt/stmtInfo.downScope linked
* list stack, if it isn't already there. If it is there, but it
* lacks the SIF_SCOPE flag, it must be a try, catch, or finally
* block.
*/
stmt->isBlockScope = stmt->isNestedScope = true;
stmt->downScope = pc->topScopeStmt;
pc->topScopeStmt = stmt;
blockObj->initEnclosingNestedScopeFromParser(pc->staticScope);
pc->staticScope = blockObj;
stmt->staticScope = blockObj;
#ifdef DEBUG
ParseNode *tmp = pc->blockNode;
MOZ_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE));
#endif
/* Create a new lexical scope node for these statements. */
ParseNode *pn1 = handler.new_<LexicalScopeNode>(blockbox, pc->blockNode);
if (!pn1)
return null();
pc->blockNode = pn1;
}
pn = variables(isConst ? PNK_CONST : PNK_LET, nullptr,
&pc->staticScope->as<StaticBlockObject>(), HoistVars);
if (!pn)
return null();
pn->pn_xflags = PNX_POPVAR;
} while (0);
/*
* Parse body-level lets without a new block object. ES6 specs
* that an execution environment's initial lexical environment
* is the VariableEnvironment, i.e., body-level lets are in
* the same environment record as vars.
*
* However, they cannot be parsed exactly as vars, as ES6
* requires that uninitialized lets throw ReferenceError on use.
*
* See 8.1.1.1.6 and the note in 13.2.1.
*
* FIXME global-level lets are still considered vars until
* other bugs are fixed.
*/
ParseNodeKind kind = PNK_LET;
if (pc->atGlobalLevel())
kind = isConst ? PNK_GLOBALCONST : PNK_VAR;
else if (isConst)
kind = PNK_CONST;
ParseNode *pn = variables(kind, nullptr,
CurrentLexicalStaticBlock(pc),
HoistVars);
if (!pn)
return null();
pn->pn_xflags = PNX_POPVAR;
return MatchOrInsertSemicolon(tokenStream) ? pn : nullptr;
}
@ -4616,7 +4652,7 @@ Parser<FullParseHandler>::forStatement()
isForEach = true;
addTelemetry(JSCompartment::DeprecatedForEach);
if (versionNumber() < JSVERSION_LATEST) {
if (!report(ParseWarning, pc->sc->strict, null(), JSMSG_DEPRECATED_FOR_EACH))
if (!report(ParseWarning, pc->sc->strict(), null(), JSMSG_DEPRECATED_FOR_EACH))
return null();
}
}
@ -5484,7 +5520,7 @@ Parser<FullParseHandler>::withStatement()
// construct that is forbidden in strict mode code, but doesn't even merit a
// warning under JSOPTION_EXTRA_WARNINGS. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=514576#c1.
if (pc->sc->strict && !report(ParseStrictError, true, null(), JSMSG_STRICT_CODE_WITH))
if (pc->sc->strict() && !report(ParseStrictError, true, null(), JSMSG_STRICT_CODE_WITH))
return null();
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH);
@ -5787,6 +5823,80 @@ Parser<ParseHandler>::debuggerStatement()
return handler.newDebuggerStatement(p);
}
template <>
ParseNode *
Parser<FullParseHandler>::classStatement()
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CLASS));
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
RootedPropertyName name(context);
if (tt == TOK_NAME) {
name = tokenStream.currentName();
} else if (tt == TOK_YIELD) {
if (!checkYieldNameValidity())
return null();
name = tokenStream.currentName();
} else {
// Class statements must have a bound name
report(ParseError, false, null(), JSMSG_UNNAMED_CLASS_STMT);
return null();
}
if (name == context->names().let) {
report(ParseError, false, null(), JSMSG_LET_CLASS_BINDING);
return null();
}
// Because the binding definitions keep track of their blockId, we need to
// create at least the inner binding later. Keep track of the name's position
// in order to provide it for the nodes created later.
TokenPos namePos = pos();
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CLASS);
bool savedStrictness = setLocalStrictMode(true);
StmtInfoPC classStmt(context);
ParseNode *classBlock = pushLexicalScope(&classStmt);
if (!classBlock)
return null();
ParseNode *classMethods = propertyList(ClassBody);
if (!classMethods)
return null();
handler.setLexicalScopeBody(classBlock, classMethods);
ParseNode *innerBinding = makeInitializedLexicalBinding(name, true, namePos);
if (!innerBinding)
return null();
PopStatementPC(tokenStream, pc);
ParseNode *outerBinding = makeInitializedLexicalBinding(name, false, namePos);
if (!outerBinding)
return null();
ParseNode *nameNode = handler.newClassNames(outerBinding, innerBinding, namePos);
if (!nameNode)
return null();
MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness));
return handler.newClass(nameNode, null(), classBlock);
}
template <>
SyntaxParseHandler::Node
Parser<SyntaxParseHandler>::classStatement()
{
JS_ALWAYS_FALSE(abortIfSyntaxParser());
return SyntaxParseHandler::NodeFailure;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::statement(bool canHaveDirectives)
@ -5854,6 +5964,11 @@ Parser<ParseHandler>::statement(bool canHaveDirectives)
return functionStmt();
case TOK_DEBUGGER:
return debuggerStatement();
case TOK_CLASS:
if (!abortIfSyntaxParser())
return null();
return classStatement();
/* TOK_CATCH and TOK_FINALLY are both handled in the TOK_TRY case */
case TOK_CATCH:
@ -6408,7 +6523,7 @@ Parser<ParseHandler>::unaryExpr(InvokedPrediction invoked)
// Per spec, deleting any unary expression is valid -- it simply
// returns true -- except for one case that is illegal in strict mode.
if (handler.isName(expr)) {
if (!report(ParseStrictError, pc->sc->strict, expr, JSMSG_DEPRECATED_DELETE_OPERAND))
if (!report(ParseStrictError, pc->sc->strict(), expr, JSMSG_DEPRECATED_DELETE_OPERAND))
return null();
pc->sc->setBindingsAccessedDynamically();
}
@ -6817,7 +6932,7 @@ Parser<FullParseHandler>::legacyComprehensionTail(ParseNode *bodyExpr, unsigned
pn2->pn_iflags |= JSITER_FOREACH;
addTelemetry(JSCompartment::DeprecatedForEach);
if (versionNumber() < JSVERSION_LATEST) {
if (!report(ParseWarning, pc->sc->strict, pn2, JSMSG_DEPRECATED_FOR_EACH))
if (!report(ParseWarning, pc->sc->strict(), pn2, JSMSG_DEPRECATED_FOR_EACH))
return null();
}
}
@ -7073,7 +7188,7 @@ Parser<ParseHandler>::generatorComprehensionLambda(GeneratorKind comprehensionKi
return null();
// Create box for fun->object early to root it.
Directives directives(/* strict = */ outerpc->sc->strict);
Directives directives(/* strict = */ outerpc->sc->strict());
FunctionBox *genFunbox = newFunctionBox(genfn, fun, outerpc, directives, comprehensionKind);
if (!genFunbox)
return null();
@ -7591,7 +7706,7 @@ Parser<ParseHandler>::memberExpr(TokenKind tt, bool allowCallSyntax, InvokedPred
if (JSAtom *atom = handler.isName(lhs)) {
if (tt == TOK_LP && atom == context->names().eval) {
/* Select JSOP_EVAL and flag pc as heavyweight. */
op = pc->sc->strict ? JSOP_STRICTEVAL : JSOP_EVAL;
op = pc->sc->strict() ? JSOP_STRICTEVAL : JSOP_EVAL;
pc->sc->setBindingsAccessedDynamically();
pc->sc->setHasDirectEval();
@ -7599,7 +7714,7 @@ Parser<ParseHandler>::memberExpr(TokenKind tt, bool allowCallSyntax, InvokedPred
* In non-strict mode code, direct calls to eval can add
* variables to the call object.
*/
if (pc->sc->isFunctionBox() && !pc->sc->strict)
if (pc->sc->isFunctionBox() && !pc->sc->strict())
pc->sc->asFunctionBox()->setHasExtensibleScope();
}
} else if (JSAtom *atom = handler.isGetProp(lhs)) {
@ -7885,15 +8000,27 @@ Parser<ParseHandler>::computedPropertyName(Node literal)
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::objectLiteral()
Parser<ParseHandler>::newPropertyListNode(PropListType type)
{
if (type == ClassBody)
return handler.newClassMethodList(pos().begin);
MOZ_ASSERT(type == ObjectLiteral);
return handler.newObjectLiteral(pos().begin);
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::propertyList(PropListType type)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC));
Node literal = handler.newObjectLiteral(pos().begin);
if (!literal)
Node propList = newPropertyListNode(type);
if (!propList)
return null();
bool seenPrototypeMutation = false;
bool seenConstructor = false;
RootedAtom atom(context);
for (;;) {
TokenKind ltok;
@ -7902,6 +8029,9 @@ Parser<ParseHandler>::objectLiteral()
if (ltok == TOK_RC)
break;
if (type == ClassBody && ltok == TOK_SEMI)
continue;
bool isGenerator = false;
if (ltok == TOK_MUL) {
isGenerator = true;
@ -7924,7 +8054,7 @@ Parser<ParseHandler>::objectLiteral()
break;
case TOK_LB: {
propname = computedPropertyName(literal);
propname = computedPropertyName(propList);
if (!propname)
return null();
break;
@ -7980,7 +8110,7 @@ Parser<ParseHandler>::objectLiteral()
if (!propname)
return null();
} else if (tt == TOK_LB) {
propname = computedPropertyName(literal);
propname = computedPropertyName(propList);
if (!propname)
return null();
} else {
@ -8017,12 +8147,30 @@ Parser<ParseHandler>::objectLiteral()
return null();
}
if (type == ClassBody) {
if (atom == context->names().constructor) {
if (isGenerator || op != JSOP_INITPROP) {
report(ParseError, false, propname, JSMSG_BAD_METHOD_DEF);
return null();
}
if (seenConstructor) {
report(ParseError, false, propname, JSMSG_DUPLICATE_PROPERTY, "constructor");
return null();
}
seenConstructor = true;
}
}
if (op == JSOP_INITPROP) {
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
if (tt == TOK_COLON) {
if (type == ClassBody) {
report(ParseError, false, null(), JSMSG_BAD_METHOD_DEF);
return null();
}
if (isGenerator) {
report(ParseError, false, null(), JSMSG_BAD_PROP_ID);
return null();
@ -8047,13 +8195,13 @@ Parser<ParseHandler>::objectLiteral()
// method/generator definitions, computed property name
// versions of all of these, and shorthands do not.
uint32_t begin = handler.getPosition(propname).begin;
if (!handler.addPrototypeMutation(literal, begin, propexpr))
if (!handler.addPrototypeMutation(propList, begin, propexpr))
return null();
} else {
if (!handler.isConstant(propexpr))
handler.setListFlag(literal, PNX_NONCONST);
handler.setListFlag(propList, PNX_NONCONST);
if (!handler.addPropertyDefinition(literal, propname, propexpr))
if (!handler.addPropertyDefinition(propList, propname, propexpr))
return null();
}
} else if (ltok == TOK_NAME && (tt == TOK_COMMA || tt == TOK_RC)) {
@ -8061,6 +8209,10 @@ Parser<ParseHandler>::objectLiteral()
* Support, e.g., |var {x, y} = o| as destructuring shorthand
* for |var {x: x, y: y} = o|, per proposed JS2/ES4 for JS1.8.
*/
if (type == ClassBody) {
report(ParseError, false, null(), JSMSG_BAD_METHOD_DEF);
return null();
}
if (isGenerator) {
report(ParseError, false, null(), JSMSG_BAD_PROP_ID);
return null();
@ -8074,11 +8226,11 @@ Parser<ParseHandler>::objectLiteral()
if (!nameExpr)
return null();
if (!handler.addShorthand(literal, propname, nameExpr))
if (!handler.addShorthand(propList, propname, nameExpr))
return null();
} else if (tt == TOK_LP) {
tokenStream.ungetToken();
if (!methodDefinition(literal, propname, Normal, Method,
if (!methodDefinition(type, propList, propname, Normal, Method,
isGenerator ? StarGenerator : NotGenerator, op)) {
return null();
}
@ -8088,32 +8240,40 @@ Parser<ParseHandler>::objectLiteral()
}
} else {
/* NB: Getter function in { get x(){} } is unnamed. */
if (!methodDefinition(literal, propname, op == JSOP_INITPROP_GETTER ? Getter : Setter,
if (!methodDefinition(type, propList, propname, op == JSOP_INITPROP_GETTER ? Getter : Setter,
Expression, NotGenerator, op)) {
return null();
}
}
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
if (tt == TOK_RC)
break;
if (tt != TOK_COMMA) {
report(ParseError, false, null(), JSMSG_CURLY_AFTER_LIST);
return null();
if (type == ObjectLiteral) {
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
if (tt == TOK_RC)
break;
if (tt != TOK_COMMA) {
report(ParseError, false, null(), JSMSG_CURLY_AFTER_LIST);
return null();
}
}
}
handler.setEndPosition(literal, pos().end);
return literal;
// Default constructors not yet implemented. See bug 1105463
if (type == ClassBody && !seenConstructor) {
report(ParseError, false, null(), JSMSG_NO_CLASS_CONSTRUCTOR);
return null();
}
handler.setEndPosition(propList, pos().end);
return propList;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::methodDefinition(Node literal, Node propname, FunctionType type,
FunctionSyntaxKind kind, GeneratorKind generatorKind,
JSOp op)
Parser<ParseHandler>::methodDefinition(PropListType listType, Node propList, Node propname,
FunctionType type, FunctionSyntaxKind kind,
GeneratorKind generatorKind, JSOp op)
{
RootedPropertyName funName(context);
if (kind == Method && tokenStream.isCurrentTokenType(TOK_NAME))
@ -8124,9 +8284,12 @@ Parser<ParseHandler>::methodDefinition(Node literal, Node propname, FunctionType
Node fn = functionDef(funName, type, kind, generatorKind);
if (!fn)
return false;
if (!handler.addMethodDefinition(literal, propname, fn, op))
return false;
return true;
if (listType == ClassBody)
return handler.addClassMethodDefinition(propList, propname, fn, op);
MOZ_ASSERT(listType == ObjectLiteral);
return handler.addObjectMethodDefinition(propList, propname, fn, op);
}
template <typename ParseHandler>
@ -8144,7 +8307,7 @@ Parser<ParseHandler>::primaryExpr(TokenKind tt, InvokedPrediction invoked)
return arrayInitializer();
case TOK_LC:
return objectLiteral();
return propertyList(ObjectLiteral);
case TOK_LET:
return deprecatedLetBlockOrExpression(LetExpression);

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

@ -301,6 +301,7 @@ struct ParseContext : public GenericParseContext
// if (cond) { function f3() { if (cond) { function f4() { } } } }
//
bool atBodyLevel() { return !topStmt; }
bool atGlobalLevel() { return atBodyLevel() && !sc->isFunctionBox() && (topStmt == topScopeStmt); }
// True if this is the ParseContext for the body of a function created by
// the Function constructor.
@ -316,7 +317,7 @@ struct ParseContext : public GenericParseContext
template <typename ParseHandler>
inline
Directives::Directives(ParseContext<ParseHandler> *parent)
: strict_(parent->sc->strict),
: strict_(parent->sc->strict()),
asmJS_(parent->useAsmOrInsideUseAsm())
{}
@ -328,6 +329,7 @@ class CompExprTransplanter;
enum LetContext { LetExpression, LetStatement };
enum VarContext { HoistVars, DontHoistVars };
enum FunctionType { Getter, Setter, Normal };
enum PropListType { ObjectLiteral, ClassBody };
template <typename ParseHandler>
class Parser : private JS::AutoGCRooter, public StrictModeGetter
@ -503,7 +505,11 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
return versionNumber() >= JSVERSION_1_7 || pc->isGenerator();
}
virtual bool strictMode() { return pc->sc->strict; }
virtual bool strictMode() { return pc->sc->strict(); }
bool setLocalStrictMode(bool strict) {
MOZ_ASSERT(tokenStream.debugHasNoLookahead());
return pc->sc->setLocalStrictMode(strict);
}
const ReadOnlyCompileOptions &options() const {
return tokenStream.options();
@ -547,6 +553,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
Node throwStatement();
Node tryStatement();
Node debuggerStatement();
Node classStatement();
Node lexicalDeclaration(bool isConst);
Node letDeclarationOrBlock();
@ -569,8 +576,8 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
Node parenExprOrGeneratorComprehension();
Node exprInParens();
bool methodDefinition(Node literal, Node propname, FunctionType type, FunctionSyntaxKind kind,
GeneratorKind generatorKind, JSOp Op);
bool methodDefinition(PropListType listType, Node propList, Node propname, FunctionType type,
FunctionSyntaxKind kind, GeneratorKind generatorKind, JSOp Op);
/*
* Additional JS parsers.
@ -651,16 +658,21 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
Node pushLexicalScope(Handle<StaticBlockObject*> blockObj, StmtInfoPC *stmt);
Node pushLetScope(Handle<StaticBlockObject*> blockObj, StmtInfoPC *stmt);
bool noteNameUse(HandlePropertyName name, Node pn);
Node objectLiteral();
Node computedPropertyName(Node literal);
Node arrayInitializer();
Node newRegExp();
Node propertyList(PropListType type);
Node newPropertyListNode(PropListType type);
bool checkAndPrepareLexical(bool isConst, const TokenPos &errorPos);
Node makeInitializedLexicalBinding(HandlePropertyName name, bool isConst, const TokenPos &pos);
Node newBindingNode(PropertyName *name, bool functionScope, VarContext varContext = HoistVars);
bool checkDestructuring(BindData<ParseHandler> *data, Node left);
bool checkDestructuringObject(BindData<ParseHandler> *data, Node objectPattern);
bool checkDestructuringArray(BindData<ParseHandler> *data, Node arrayPattern);
bool bindDestructuringVar(BindData<ParseHandler> *data, Node pn);
bool bindInitialized(BindData<ParseHandler> *data, Node pn);
bool bindDestructuringLHS(Node pn);
bool makeSetCall(Node pn, unsigned msg);
Node cloneDestructuringDefault(Node opn);

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

@ -178,7 +178,8 @@ class SharedContext
public:
ExclusiveContext *const context;
AnyContextFlags anyCxFlags;
bool strict;
bool strictScript;
bool localStrict;
bool extraWarnings;
// If it's function code, funbox must be non-nullptr and scopeChain must be
@ -186,7 +187,8 @@ class SharedContext
SharedContext(ExclusiveContext *cx, Directives directives, bool extraWarnings)
: context(cx),
anyCxFlags(),
strict(directives.strict()),
strictScript(directives.strict()),
localStrict(false),
extraWarnings(extraWarnings)
{}
@ -208,9 +210,18 @@ class SharedContext
inline bool allLocalsAliased();
bool strict() {
return strictScript || localStrict;
}
bool setLocalStrictMode(bool strict) {
bool retVal = localStrict;
localStrict = strict;
return retVal;
}
// JSOPTION_EXTRA_WARNINGS warnings or strict mode errors.
bool needStrictChecks() {
return strict || extraWarnings;
return strict() || extraWarnings;
}
bool isDotVariable(JSAtom *atom) const {

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

@ -186,10 +186,12 @@ class SyntaxParseHandler
void addArrayElement(Node literal, Node element) { }
Node newObjectLiteral(uint32_t begin) { return NodeGeneric; }
Node newClassMethodList(uint32_t begin) { return NodeGeneric; }
bool addPrototypeMutation(Node literal, uint32_t begin, Node expr) { return true; }
bool addPropertyDefinition(Node literal, Node name, Node expr) { return true; }
bool addShorthand(Node literal, Node name, Node expr) { return true; }
bool addMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; }
bool addObjectMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; }
bool addClassMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; }
Node newYieldExpression(uint32_t begin, Node value, Node gen) { return NodeUnparenthesizedYieldExpr; }
Node newYieldStarExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; }

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

@ -110,6 +110,7 @@
macro(LET, "keyword 'let'") \
macro(EXPORT, "keyword 'export'") \
macro(IMPORT, "keyword 'import'") \
macro(CLASS, "keyword 'class'") \
macro(RESERVED, "reserved keyword") \
/* reserved keywords in strict mode */ \
macro(STRICT_RESERVED, "reserved keyword") \

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

@ -976,8 +976,14 @@ TokenStream::putIdentInTokenbuf(const char16_t *identStart)
bool
TokenStream::checkForKeyword(const KeywordInfo *kw, TokenKind *ttp)
{
if (kw->tokentype == TOK_RESERVED)
if (kw->tokentype == TOK_RESERVED
#ifndef JS_HAS_CLASSES
|| kw->tokentype == TOK_CLASS
#endif
)
{
return reportError(JSMSG_RESERVED_ID, kw->chars);
}
if (kw->tokentype != TOK_STRICT_RESERVED) {
if (kw->version <= versionNumber()) {

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

@ -531,6 +531,11 @@ class MOZ_STACK_CLASS TokenStream
void tell(Position *);
void seek(const Position &pos);
bool seek(const Position &pos, const TokenStream &other);
#ifdef DEBUG
inline bool debugHasNoLookahead() const {
return lookahead == 0;
}
#endif
const char16_t *rawCharPtrAt(size_t offset) const {
return userbuf.rawCharPtrAt(offset);

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

@ -802,11 +802,11 @@ TypeSet::MarkTypeRoot(JSTracer *trc, TypeSet::Type *v, const char *name)
{
JS_ROOT_MARKING_ASSERT(trc);
trc->setTracingName(name);
if (v->isSingleton()) {
if (v->isSingletonUnchecked()) {
JSObject *obj = v->singleton();
MarkInternal(trc, &obj);
*v = TypeSet::ObjectType(obj);
} else if (v->isGroup()) {
} else if (v->isGroupUnchecked()) {
ObjectGroup *group = v->group();
MarkInternal(trc, &group);
*v = TypeSet::ObjectType(group);

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