Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-03-29 14:28:13 +02:00
commit 01caadd25e
29 changed files with 468 additions and 150 deletions

View File

@ -1,6 +1,5 @@
function test() {
var tab = gBrowser.addTab(null, {skipAnimation: true});
gBrowser.selectedTab = tab;
add_task(function* test() {
var tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
var gotTabAttrModified = false;
var gotTabClose = false;
@ -16,11 +15,11 @@ function test() {
tab.addEventListener("TabClose", onTabClose, false);
gBrowser.removeTab(tab);
yield BrowserTestUtils.removeTab(tab);
ok(gotTabClose, "should have got the TabClose event");
ok(!gotTabAttrModified, "shouldn't have got the TabAttrModified event after TabClose");
tab.removeEventListener("TabClose", onTabClose, false);
tab.removeEventListener("TabAttrModified", onTabAttrModified, false);
}
});

View File

@ -7,6 +7,7 @@
"EventEmitter": true,
"IconDetails": true,
"makeWidgetId": true,
"pageActionFor": true,
"PanelPopup": true,
"TabContext": true,
"ViewPopup": true,

View File

@ -17,10 +17,6 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// WeakMap[Extension -> BrowserAction]
var browserActionMap = new WeakMap();
function browserActionOf(extension) {
return browserActionMap.get(extension);
}
// Responsible for the browser_action section of the manifest as well
// as the associated popup.
function BrowserAction(options, extension) {
@ -203,6 +199,12 @@ BrowserAction.prototype = {
},
};
BrowserAction.for = (extension) => {
return browserActionMap.get(extension);
};
global.browserActionFor = BrowserAction.for;
/* eslint-disable mozilla/balanced-listeners */
extensions.on("manifest_browser_action", (type, directive, extension, manifest) => {
let browserAction = new BrowserAction(manifest.browser_action, extension);
@ -226,20 +228,20 @@ extensions.registerSchemaAPI("browserAction", null, (extension, context) => {
let tab = TabManager.activeTab;
fire(TabManager.convert(extension, tab));
};
browserActionOf(extension).on("click", listener);
BrowserAction.for(extension).on("click", listener);
return () => {
browserActionOf(extension).off("click", listener);
BrowserAction.for(extension).off("click", listener);
};
}).api(),
enable: function(tabId) {
let tab = tabId !== null ? TabManager.getTab(tabId) : null;
browserActionOf(extension).setProperty(tab, "enabled", true);
BrowserAction.for(extension).setProperty(tab, "enabled", true);
},
disable: function(tabId) {
let tab = tabId !== null ? TabManager.getTab(tabId) : null;
browserActionOf(extension).setProperty(tab, "enabled", false);
BrowserAction.for(extension).setProperty(tab, "enabled", false);
},
setTitle: function(details) {
@ -250,30 +252,30 @@ extensions.registerSchemaAPI("browserAction", null, (extension, context) => {
if (tab && title == "") {
title = null;
}
browserActionOf(extension).setProperty(tab, "title", title);
BrowserAction.for(extension).setProperty(tab, "title", title);
},
getTitle: function(details) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let title = browserActionOf(extension).getProperty(tab, "title");
let title = BrowserAction.for(extension).getProperty(tab, "title");
return Promise.resolve(title);
},
setIcon: function(details) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let icon = IconDetails.normalize(details, extension, context);
browserActionOf(extension).setProperty(tab, "icon", icon);
BrowserAction.for(extension).setProperty(tab, "icon", icon);
return Promise.resolve();
},
setBadgeText: function(details) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
browserActionOf(extension).setProperty(tab, "badgeText", details.text);
BrowserAction.for(extension).setProperty(tab, "badgeText", details.text);
},
getBadgeText: function(details) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let text = browserActionOf(extension).getProperty(tab, "badgeText");
let text = BrowserAction.for(extension).getProperty(tab, "badgeText");
return Promise.resolve(text);
},
@ -285,23 +287,23 @@ extensions.registerSchemaAPI("browserAction", null, (extension, context) => {
// For internal consistency, we currently resolve both relative to the
// calling context.
let url = details.popup && context.uri.resolve(details.popup);
browserActionOf(extension).setProperty(tab, "popup", url);
BrowserAction.for(extension).setProperty(tab, "popup", url);
},
getPopup: function(details) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let popup = browserActionOf(extension).getProperty(tab, "popup");
let popup = BrowserAction.for(extension).getProperty(tab, "popup");
return Promise.resolve(popup);
},
setBadgeBackgroundColor: function(details) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
browserActionOf(extension).setProperty(tab, "badgeBackgroundColor", details.color);
BrowserAction.for(extension).setProperty(tab, "badgeBackgroundColor", details.color);
},
getBadgeBackgroundColor: function(details, callback) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let color = browserActionOf(extension).getProperty(tab, "badgeBackgroundColor");
let color = BrowserAction.for(extension).getProperty(tab, "badgeBackgroundColor");
return Promise.resolve(color);
},
},

View File

@ -15,10 +15,17 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// WeakMap[Extension -> CommandList]
var commandsMap = new WeakMap();
function CommandList(commandsObj, extensionID) {
this.commands = this.loadCommandsFromManifest(commandsObj);
this.keysetID = `ext-keyset-id-${makeWidgetId(extensionID)}`;
function CommandList(manifest, extension) {
this.extension = extension;
this.id = makeWidgetId(extension.id);
this.windowOpenListener = null;
// Map[{String} commandName -> {Object} commandProperties]
this.commands = this.loadCommandsFromManifest(manifest);
// WeakMap[Window -> <xul:keyset>]
this.keysetsMap = new WeakMap();
this.register();
EventEmitter.decorate(this);
}
@ -30,11 +37,13 @@ CommandList.prototype = {
*/
register() {
for (let window of WindowListManager.browserWindows()) {
this.registerKeysToDocument(window.document);
this.registerKeysToDocument(window);
}
this.windowOpenListener = (window) => {
this.registerKeysToDocument(window.document);
if (!this.keysetsMap.has(window)) {
this.registerKeysToDocument(window);
}
};
WindowListManager.addOpenListener(this.windowOpenListener);
@ -46,9 +55,8 @@ CommandList.prototype = {
*/
unregister() {
for (let window of WindowListManager.browserWindows()) {
let keyset = window.document.getElementById(this.keysetID);
if (keyset) {
keyset.remove();
if (this.keysetsMap.has(window)) {
this.keysetsMap.get(window).remove();
}
}
@ -57,15 +65,15 @@ CommandList.prototype = {
/**
* Creates a Map from commands for each command in the manifest.commands object.
* @param {Object} commandsObj The manifest.commands JSON object.
* @param {Object} manifest The manifest JSON object.
*/
loadCommandsFromManifest(commandsObj) {
loadCommandsFromManifest(manifest) {
let commands = new Map();
// For Windows, chrome.runtime expects 'win' while chrome.commands
// expects 'windows'. We can special case this for now.
let os = PlatformInfo.os == "win" ? "windows" : PlatformInfo.os;
for (let name of Object.keys(commandsObj)) {
let command = commandsObj[name];
for (let name of Object.keys(manifest.commands)) {
let command = manifest.commands[name];
commands.set(name, {
description: command.description,
shortcut: command.suggested_key[os] || command.suggested_key.default,
@ -76,16 +84,18 @@ CommandList.prototype = {
/**
* Registers the commands to a document.
* @param {Document} doc The XUL document to insert the Keyset.
* @param {ChromeWindow} window The XUL window to insert the Keyset.
*/
registerKeysToDocument(doc) {
registerKeysToDocument(window) {
let doc = window.document;
let keyset = doc.createElementNS(XUL_NS, "keyset");
keyset.id = this.keysetID;
keyset.id = `ext-keyset-id-${this.id}`;
this.commands.forEach((command, name) => {
let keyElement = this.buildKey(doc, name, command.shortcut);
keyset.appendChild(keyElement);
});
doc.documentElement.appendChild(keyset);
this.keysetsMap.set(window, keyset);
},
/**
@ -110,7 +120,12 @@ CommandList.prototype = {
// We remove all references to the key elements when the extension is shutdown,
// therefore the listeners for these elements will be garbage collected.
keyElement.addEventListener("command", (event) => {
this.emit("command", name);
if (name == "_execute_page_action") {
let win = event.target.ownerDocument.defaultView;
pageActionFor(this.extension).triggerAction(win);
} else {
this.emit("command", name);
}
});
/* eslint-enable mozilla/balanced-listeners */
@ -195,7 +210,7 @@ CommandList.prototype = {
/* eslint-disable mozilla/balanced-listeners */
extensions.on("manifest_commands", (type, directive, extension, manifest) => {
commandsMap.set(extension, new CommandList(manifest.commands, extension.id));
commandsMap.set(extension, new CommandList(manifest, extension));
});
extensions.on("shutdown", (type, extension) => {

View File

@ -2,6 +2,7 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
EventManager,
@ -10,7 +11,6 @@ var {
// WeakMap[Extension -> PageAction]
var pageActionMap = new WeakMap();
// Handles URL bar icons, including the |page_action| manifest entry
// and associated API.
function PageAction(options, extension) {
@ -123,6 +123,19 @@ PageAction.prototype = {
return this.buttons.get(window);
},
/**
* Triggers this page action for the given window, with the same effects as
* if it were clicked by a user.
*
* This has no effect if the page action is hidden for the selected tab.
*/
triggerAction(window) {
let pageAction = pageActionMap.get(this.extension);
if (pageAction.getProperty(window.gBrowser.selectedTab, "show")) {
pageAction.handleClick(window);
}
},
// Handles a click event on the page action button for the given
// window.
// If the page action has a |popup| property, a panel is opened to
@ -163,11 +176,6 @@ PageAction.prototype = {
},
};
PageAction.for = extension => {
return pageActionMap.get(extension);
};
/* eslint-disable mozilla/balanced-listeners */
extensions.on("manifest_page_action", (type, directive, extension, manifest) => {
let pageAction = new PageAction(manifest.page_action, extension);
@ -182,6 +190,11 @@ extensions.on("shutdown", (type, extension) => {
});
/* eslint-enable mozilla/balanced-listeners */
PageAction.for = extension => {
return pageActionMap.get(extension);
};
global.pageActionFor = PageAction.for;
extensions.registerSchemaAPI("pageAction", null, (extension, context) => {
return {

View File

@ -24,6 +24,7 @@ support-files =
[browser_ext_browserAction_popup.js]
[browser_ext_popup_api_injection.js]
[browser_ext_contextMenus.js]
[browser_ext_commands_execute_page_action.js]
[browser_ext_commands_getAll.js]
[browser_ext_commands_onCommand.js]
[browser_ext_getViews.js]

View File

@ -0,0 +1,133 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* test_execute_page_action_without_popup() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"commands": {
"_execute_page_action": {
"suggested_key": {
"default": "Alt+Shift+J",
},
},
"send-keys-command": {
"suggested_key": {
"default": "Alt+Shift+3",
},
},
},
"page_action": {},
},
background: function() {
let isShown = false;
browser.commands.onCommand.addListener((commandName) => {
if (commandName == "_execute_page_action") {
browser.test.fail(`The onCommand listener should never fire for ${commandName}.`);
} else if (commandName == "send-keys-command") {
if (!isShown) {
isShown = true;
browser.tabs.query({currentWindow: true, active: true}, tabs => {
tabs.forEach(tab => {
browser.pageAction.show(tab.id);
});
browser.test.sendMessage("send-keys");
});
}
}
});
browser.pageAction.onClicked.addListener(() => {
browser.test.assertTrue(isShown, "The onClicked event should fire if the page action is shown.");
browser.test.notifyPass("page-action-without-popup");
});
browser.test.sendMessage("send-keys");
},
});
extension.onMessage("send-keys", () => {
EventUtils.synthesizeKey("j", {altKey: true, shiftKey: true});
EventUtils.synthesizeKey("3", {altKey: true, shiftKey: true});
});
yield extension.startup();
yield extension.awaitFinish("page-action-without-popup");
yield extension.unload();
});
add_task(function* test_execute_page_action_with_popup() {
let scriptPage = url => `<html><head><meta charset="utf-8"><script src="${url}"></script></head><body>Test Popup</body></html>`;
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"commands": {
"_execute_page_action": {
"suggested_key": {
"default": "Alt+Shift+J",
},
},
"send-keys-command": {
"suggested_key": {
"default": "Alt+Shift+3",
},
},
},
"page_action": {
"default_popup": "popup.html",
},
},
files: {
"popup.html": scriptPage("popup.js"),
"popup.js": function() {
browser.runtime.sendMessage("popup-opened");
},
},
background: function() {
let isShown = false;
browser.commands.onCommand.addListener((message) => {
if (message == "_execute_page_action") {
browser.test.fail(`The onCommand listener should never fire for ${message}.`);
}
if (message == "send-keys-command") {
if (!isShown) {
isShown = true;
browser.tabs.query({currentWindow: true, active: true}, tabs => {
tabs.forEach(tab => {
browser.pageAction.show(tab.id);
});
browser.test.sendMessage("send-keys");
});
}
}
});
browser.pageAction.onClicked.addListener(() => {
browser.test.fail(`The onClicked listener should never fire when the pageAction has a popup.`);
});
browser.runtime.onMessage.addListener(msg => {
browser.test.assertEq(msg, "popup-opened", "expected popup opened");
browser.test.assertTrue(isShown, "The onClicked event should fire if the page action is shown.");
browser.test.notifyPass("page-action-with-popup");
});
browser.test.sendMessage("send-keys");
},
});
extension.onMessage("send-keys", () => {
EventUtils.synthesizeKey("j", {altKey: true, shiftKey: true});
EventUtils.synthesizeKey("3", {altKey: true, shiftKey: true});
});
yield extension.startup();
yield extension.awaitFinish("page-action-with-popup");
yield extension.unload();
});

View File

@ -2,7 +2,7 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* () {
add_task(function* test_user_defined_commands() {
// Create a window before the extension is loaded.
let win1 = yield BrowserTestUtils.openNewBrowserWindow();
yield BrowserTestUtils.loadURI(win1.gBrowser.selectedBrowser, "about:robots");
@ -10,7 +10,6 @@ add_task(function* () {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"name": "Commands Extension",
"commands": {
"toggle-feature-using-alt-shift-3": {
"suggested_key": {
@ -27,8 +26,8 @@ add_task(function* () {
},
background: function() {
browser.commands.onCommand.addListener((message) => {
browser.test.sendMessage("oncommand", message);
browser.commands.onCommand.addListener((commandName) => {
browser.test.sendMessage("oncommand", commandName);
});
browser.test.sendMessage("ready");
},
@ -53,10 +52,12 @@ add_task(function* () {
// Confirm the keysets have been added to both windows.
let keysetID = `ext-keyset-id-${makeWidgetId(extension.id)}`;
let keyset = win1.document.getElementById(keysetID);
is(keyset.childNodes.length, 2, "Expected keyset to exist and have 2 children");
ok(keyset != null, "Expected keyset to exist");
is(keyset.childNodes.length, 2, "Expected keyset to have 2 children");
keyset = win2.document.getElementById(keysetID);
is(keyset.childNodes.length, 2, "Expected keyset to exist and have 2 children");
ok(keyset != null, "Expected keyset to exist");
is(keyset.childNodes.length, 2, "Expected keyset to have 2 children");
// Confirm that the commands are registered to both windows.
yield focusWindow(win1);
@ -84,3 +85,5 @@ add_task(function* () {
SimpleTest.endMonitorConsole();
yield waitForConsole;
});

View File

@ -116,8 +116,7 @@ function _removeOrDisableBreakpoint(location, isDisabled) {
return dispatch(Object.assign({}, action, {
[PROMISE]: bpClient.remove()
}));
}
else {
} else {
return dispatch(Object.assign({}, action, { status: "done" }));
}
}
@ -154,21 +153,30 @@ function setBreakpointCondition(location, condition) {
}
const bpClient = getBreakpointClient(bp.actor);
return dispatch({
const action = {
type: constants.SET_BREAKPOINT_CONDITION,
breakpoint: bp,
condition: condition,
[PROMISE]: Task.spawn(function*() {
const newClient = yield bpClient.setCondition(gThreadClient, condition);
condition: condition
};
// Remove the old instance and save the new one
setBreakpointClient(bpClient.actor, null);
setBreakpointClient(newClient.actor, newClient);
// If it's not disabled, we need to update the condition on the
// server. Otherwise, just dispatch a non-remote action that
// updates the condition locally.
if(!bp.disabled) {
return dispatch(Object.assign({}, action, {
[PROMISE]: Task.spawn(function*() {
const newClient = yield bpClient.setCondition(gThreadClient, condition);
return { actor: newClient.actor };
})
});
// Remove the old instance and save the new one
setBreakpointClient(bpClient.actor, null);
setBreakpointClient(newClient.actor, newClient);
return { actor: newClient.actor };
})
}));
} else {
return dispatch(action);
}
};
}

View File

@ -117,7 +117,14 @@ function update(state = initialState, action, emitChange) {
const bp = state.breakpoints[id];
emitChange("breakpoint-condition-updated", bp);
if (action.status === 'start') {
if (!action.status) {
// No status means that it wasn't a remote request. Just update
// the condition locally.
return mergeIn(state, ['breakpoints', id], {
condition: action.condition
});
}
else if (action.status === 'start') {
return mergeIn(state, ['breakpoints', id], {
loading: true,
condition: action.condition
@ -126,6 +133,7 @@ function update(state = initialState, action, emitChange) {
else if (action.status === 'done') {
return mergeIn(state, ['breakpoints', id], {
loading: false,
condition: action.condition,
// Setting a condition creates a new breakpoint client as of
// now, so we need to update the actor
actor: action.value.actor

View File

@ -67,8 +67,15 @@ function update(state = initialState, action, emitChange) {
loading: false
});
// If it errored, just display the source as it way before.
emitChange('prettyprinted', s.sources[action.source.actor]);
// If it errored, just display the source as it was before, but
// only if there is existing text already. If auto-prettifying
// is on, the original text may still be coming in and we don't
// have it yet. If we try to set empty text we confuse the
// editor because it thinks it's already displaying the source's
// text and won't load the text when it actually comes in.
if(s.sourcesText[action.source.actor].text != null) {
emitChange('prettyprinted', s.sources[action.source.actor]);
}
}
else {
s = _updateText(state, action);

View File

@ -82,6 +82,7 @@ var DebuggerView = {
this._initializeVariablesView();
this._editorSource = {};
this._editorDocuments = {};
document.title = L10N.getStr("DebuggerWindowTitle");
@ -374,14 +375,15 @@ var DebuggerView = {
if (source && source.actor === location.actor) {
this.editor.removeBreakpoint(location.line - 1);
this.editor.removeBreakpointCondition(location.line - 1);
}
},
renderEditorBreakpointCondition: function (breakpoint) {
const { location, condition } = breakpoint;
const { location, condition, disabled } = breakpoint;
const source = queries.getSelectedSource(this.controller.getState());
if (source && source.actor === location.actor) {
if (source && source.actor === location.actor && !disabled) {
if (condition) {
this.editor.setBreakpointCondition(location.line - 1);
} else {
@ -415,14 +417,28 @@ var DebuggerView = {
* Sets the currently displayed text contents in the source editor.
* This resets the mode and undo stack.
*
* @param string documentKey
* Key to get the correct editor document
*
* @param string aTextContent
* The source text content.
*
* @param boolean shouldUpdateText
Forces a text and mode reset
*/
_setEditorText: function(aTextContent = "") {
this.editor.setMode(Editor.modes.text);
this.editor.setText(aTextContent);
_setEditorText: function(documentKey, aTextContent = "", shouldUpdateText = false) {
const isNew = this._setEditorDocument(documentKey);
this.editor.clearDebugLocation();
this.editor.clearHistory();
this.editor.setCursor({ line: 0, ch: 0});
this.editor.removeBreakpoints();
// Only set editor's text and mode if it is a new document
if (isNew || shouldUpdateText) {
this.editor.setMode(Editor.modes.text);
this.editor.setText(aTextContent);
}
},
/**
@ -452,6 +468,29 @@ var DebuggerView = {
this.editor.setMode(Editor.modes.text);
},
/**
* Sets the editor's displayed document.
* If there isn't a document for the source, create one
*
* @param string key - key used to access the editor document cache
*
* @return boolean isNew - was the document just created
*/
_setEditorDocument: function(key) {
let isNew;
if (!this._editorDocuments[key]) {
isNew = true;
this._editorDocuments[key] = this.editor.createDocument();
} else {
isNew = false;
}
const doc = this._editorDocuments[key];
this.editor.replaceDocument(doc);
return isNew;
},
renderBlackBoxed: function(source) {
this._renderSourceText(
source,
@ -477,6 +516,7 @@ var DebuggerView = {
_renderSourceText: function(source, textInfo, opts = {}) {
const selectedSource = queries.getSelectedSource(this.controller.getState());
// Exit early if we're attempting to render an unselected source
if (!selectedSource || selectedSource.actor !== source.actor) {
return;
}
@ -496,12 +536,12 @@ var DebuggerView = {
// TODO: bug 1228866, we need to update `_editorSource` here but
// still make the editor be updated when the full text comes
// through somehow.
this._setEditorText(L10N.getStr("loadingText"));
this._setEditorText('loading', L10N.getStr("loadingText"));
return;
}
else if (textInfo.error) {
let msg = L10N.getFormatStr("errorLoadingText2", textInfo.error);
this._setEditorText(msg);
this._setEditorText('error', msg);
Cu.reportError(msg);
dumpn(msg);
@ -528,14 +568,18 @@ var DebuggerView = {
return;
}
let { text, contentType } = textInfo;
let shouldUpdateText = this._editorSource.prettyPrinted != source.isPrettyPrinted;
this._setEditorText(source.actor, text, shouldUpdateText);
this._editorSource.actor = source.actor;
this._editorSource.prettyPrinted = source.isPrettyPrinted;
this._editorSource.blackboxed = source.isBlackBoxed;
this._editorSource.prettyPrinted = source.isPrettyPrinted;
let { text, contentType } = textInfo;
this._setEditorText(text);
this._setEditorMode(source.url, contentType, text);
this.updateEditorBreakpoints(source);
setTimeout(() => {
window.emit(EVENTS.SOURCE_SHOWN, source);
}, 0);
@ -787,7 +831,6 @@ var DebuggerView = {
*/
handleTabNavigation: function() {
dumpn("Handling tab navigation in the DebuggerView");
this.Filtering.clearSearch();
this.GlobalSearch.clearView();
this.StackFrames.empty();
@ -800,6 +843,7 @@ var DebuggerView = {
this.editor.setText("");
this.editor.clearHistory();
this._editorSource = {};
this._editorDocuments = {};
}
},

View File

@ -25,6 +25,13 @@ function test() {
var client = gPanel.target.client;
client.mainRoot.traits.conditionalBreakpoints = false;
function waitForConditionUpdate() {
// This will close the popup and send another request to update
// the condition
gSources._hideConditionalPopup();
return waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION);
}
Task.spawn(function*() {
yield waitForSourceAndCaretAndScopes(gPanel, ".html", 17);
const location = { actor: gSources.selectedValue, line: 18 };
@ -36,7 +43,7 @@ function test() {
const bp = queries.getBreakpoint(getState(), location);
is(bp.condition, "hello", "The conditional expression is correct.");
const finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
EventUtils.sendMouseEvent({ type: "click" },
gDebugger.document.querySelector(".dbg-breakpoint"),
gDebugger);
@ -45,6 +52,18 @@ function test() {
const textbox = gDebugger.document.getElementById("conditional-breakpoint-panel-textbox");
is(textbox.value, "hello", "The expression is correct (2).")
yield waitForConditionUpdate();
yield actions.disableBreakpoint(location);
yield actions.setBreakpointCondition(location, "foo");
yield actions.addBreakpoint(location);
finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
EventUtils.sendMouseEvent({ type: "click" },
gDebugger.document.querySelector(".dbg-breakpoint"),
gDebugger);
yield finished;
is(textbox.value, "foo", "The expression is correct (3).")
// Reset traits back to default value
client.mainRoot.traits.conditionalBreakpoints = true;
resumeDebuggerThenCloseAndFinish(gPanel);

View File

@ -21,6 +21,13 @@ function test() {
const actions = bindActionCreators(gPanel);
const getState = gDebugger.DebuggerController.getState;
function waitForConditionUpdate() {
// This will close the popup and send another request to update
// the condition
gSources._hideConditionalPopup();
return waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION);
}
Task.spawn(function*() {
yield waitForSourceAndCaretAndScopes(gPanel, ".html", 17);
const location = { actor: gSources.selectedValue, line: 18 };
@ -32,7 +39,7 @@ function test() {
const bp = queries.getBreakpoint(getState(), location);
is(bp.condition, "hello", "The conditional expression is correct.");
const finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
EventUtils.sendMouseEvent({ type: "click" },
gDebugger.document.querySelector(".dbg-breakpoint"),
gDebugger);
@ -41,6 +48,18 @@ function test() {
const textbox = gDebugger.document.getElementById("conditional-breakpoint-panel-textbox");
is(textbox.value, "hello", "The expression is correct (2).")
yield waitForConditionUpdate();
yield actions.disableBreakpoint(location);
yield actions.setBreakpointCondition(location, "foo");
yield actions.addBreakpoint(location);
finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
EventUtils.sendMouseEvent({ type: "click" },
gDebugger.document.querySelector(".dbg-breakpoint"),
gDebugger);
yield finished;
is(textbox.value, "foo", "The expression is correct (3).")
yield resumeDebuggerThenCloseAndFinish(gPanel);
});

View File

@ -89,11 +89,6 @@ function makeStateBroadcaster(stillAliveFunc) {
if (stillAliveFunc()) {
enqueuedChanges.forEach(([name, payload]) => {
if (listeners[name]) {
let payloadStr = payload;
try {
payloadStr = JSON.stringify(payload);
}
catch(e) {}
listeners[name].forEach(listener => {
listener(payload)
});

View File

@ -173,9 +173,29 @@ function addBreakpoint(ctx, line, cond) {
return deferred.promise;
}
/**
* Helps reset the debugger's breakpoint state
* - removes the breakpoints in the editor
* - cleares the debugger's breakpoint state
*
* Note, does not *actually* remove a source's breakpoints.
* The canonical state is kept in the app state.
*
*/
function removeBreakpoints(ctx) {
let { ed, cm } = ctx;
let meta = dbginfo.get(ed);
if (meta.breakpoints != null) {
meta.breakpoints = {};
}
cm.doc.iter((line) => { removeBreakpoint(ctx, line) });
}
/**
* Removes a visual breakpoint from a specified line and
* makes Editor to emit a breakpointRemoved event.
* makes Editor emit a breakpointRemoved event.
*/
function removeBreakpoint(ctx, line) {
if (!hasBreakpoint(ctx, line)) {
@ -303,7 +323,7 @@ function findPrev(ctx, query) {
[
initialize, hasBreakpoint, addBreakpoint, removeBreakpoint, moveBreakpoint,
setBreakpointCondition, removeBreakpointCondition, getBreakpoints,
setBreakpointCondition, removeBreakpointCondition, getBreakpoints, removeBreakpoints,
setDebugLocation, getDebugLocation, clearDebugLocation, find, findNext,
findPrev
].forEach(func => module.exports[func.name] = func);

View File

@ -241,6 +241,7 @@ Editor.prototype = {
container: null,
version: null,
config: null,
Doc: null,
/**
* Appends the current Editor instance to the element specified by
@ -302,6 +303,7 @@ Editor.prototype = {
// context menus won't work).
cm = win.CodeMirror(win.document.body, this.config);
this.Doc = win.CodeMirror.Doc;
// Disable APZ for source editors. It currently causes the line numbers to
// "tear off" and swim around on top of the content. Bug 1160601 tracks
@ -488,6 +490,22 @@ Editor.prototype = {
Services.scriptloader.loadSubScript(url, win, "utf8");
},
/**
* Creates a CodeMirror Document
* @returns CodeMirror.Doc
*/
createDocument: function() {
return new this.Doc("");
},
/**
* Replaces the current document with a new source document
*/
replaceDocument: function(doc) {
let cm = editors.get(this);
cm.swapDoc(doc);
},
/**
* Changes the value of a currently used highlighting mode.
* See Editor.modes for the list of all supported modes.

View File

@ -1,6 +1,7 @@
<!-- 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/. -->
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#babec3">
<path d="M8,1.1c-3.8,0-6.9,3-6.9,6.9s3,6.9,6.9,6.9s6.9-3,6.9-6.9S11.8,1.1,8,1.1z M13.1,7.9c0,1.1-0.4,2.1-1,3L5,3.8 c0.8-0.6,1.9-1,3-1C10.8,2.8,13.1,5.1,13.1,7.9z M2.9,7.9c0-1.1,0.3-2.1,0.9-2.9l7.1,7.1C10.1,12.7,9.1,13,8,13 C5.2,13,2.9,10.7,2.9,7.9z"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="#babec3">
<path d="M6 3h3V2c0-.003-3 0-3 0-.002 0 0 1 0 1zm-5 .5c0-.276.226-.5.494-.5h12.012c.273 0 .494.232.494.5 0 .276-.226.5-.494.5H1.494C1.22 4 1 3.768 1 3.5zM5 3V2c0-.553.444-1 1-1h3c.552 0 1 .443 1 1v1H5z"/>
<path d="M5 13h1V7H5v6zm4 0h1V7H9v6zm3-8v8.998c-.046.553-.45 1.002-1 1.002H4c-.55 0-.954-.456-1-1.002V5h9zm-5 8h1V7H7v6z"/>
</svg>

Before

Width:  |  Height:  |  Size: 571 B

After

Width:  |  Height:  |  Size: 651 B

View File

@ -107,7 +107,7 @@ HeapSnapshot::Create(JSContext* cx,
template<typename MessageType>
static bool
parseMessage(ZeroCopyInputStream& stream, MessageType& message)
parseMessage(ZeroCopyInputStream& stream, uint32_t sizeOfMessage, MessageType& message)
{
// We need to create a new `CodedInputStream` for each message so that the
// 64MB limit is applied per-message rather than to the whole stream.
@ -122,16 +122,7 @@ parseMessage(ZeroCopyInputStream& stream, MessageType& message)
// non-dominating messages.
codedStream.SetRecursionLimit(HeapSnapshot::MAX_STACK_DEPTH * 3);
// Because protobuf messages aren't self-delimiting, we serialize each message
// preceeded by its size in bytes. When deserializing, we read this size and
// then limit reading from the stream to the given byte size. If we didn't,
// then the first message would consume the entire stream.
uint32_t size = 0;
if (NS_WARN_IF(!codedStream.ReadVarint32(&size)))
return false;
auto limit = codedStream.PushLimit(size);
auto limit = codedStream.PushLimit(sizeOfMessage);
if (NS_WARN_IF(!message.ParseFromCodedStream(&codedStream)) ||
NS_WARN_IF(!codedStream.ConsumedEntireMessage()) ||
NS_WARN_IF(codedStream.BytesUntilLimit() != 0))
@ -391,27 +382,16 @@ HeapSnapshot::saveStackFrame(const protobuf::StackFrame& frame,
#undef GET_STRING_OR_REF_WITH_PROP_NAMES
#undef GET_STRING_OR_REF
static inline bool
StreamHasData(GzipInputStream& stream)
// Because protobuf messages aren't self-delimiting, we serialize each message
// preceded by its size in bytes. When deserializing, we read this size and then
// limit reading from the stream to the given byte size. If we didn't, then the
// first message would consume the entire stream.
static bool
readSizeOfNextMessage(ZeroCopyInputStream& stream, uint32_t* sizep)
{
// Test for the end of the stream. The protobuf library gives no way to tell
// the difference between an underlying read error and the stream being
// done. All we can do is attempt to read data and extrapolate guestimations
// from the result of that operation.
const void* buf;
int size;
bool more = stream.Next(&buf, &size);
if (!more)
// Could not read any more data. We are optimistic and assume the stream is
// just exhausted and there is not an underlying IO error, since this
// function is only called at message boundaries.
return false;
// There is more data still available in the stream. Return the data we read
// to the stream and let the parser get at it.
stream.BackUp(size);
return true;
MOZ_ASSERT(sizep);
CodedInputStream codedStream(&stream);
return codedStream.ReadVarint32(sizep) && *sizep > 0;
}
bool
@ -422,11 +402,14 @@ HeapSnapshot::init(JSContext* cx, const uint8_t* buffer, uint32_t size)
ArrayInputStream stream(buffer, size);
GzipInputStream gzipStream(&stream);
uint32_t sizeOfMessage = 0;
// First is the metadata.
protobuf::Metadata metadata;
if (!parseMessage(gzipStream, metadata))
if (NS_WARN_IF(!readSizeOfNextMessage(gzipStream, &sizeOfMessage)))
return false;
if (!parseMessage(gzipStream, sizeOfMessage, metadata))
return false;
if (metadata.has_timestamp())
timestamp.emplace(metadata.timestamp());
@ -434,7 +417,9 @@ HeapSnapshot::init(JSContext* cx, const uint8_t* buffer, uint32_t size)
// Next is the root node.
protobuf::Node root;
if (!parseMessage(gzipStream, root))
if (NS_WARN_IF(!readSizeOfNextMessage(gzipStream, &sizeOfMessage)))
return false;
if (!parseMessage(gzipStream, sizeOfMessage, root))
return false;
// Although the id is optional in the protobuf format for future proofing, we
@ -453,9 +438,13 @@ HeapSnapshot::init(JSContext* cx, const uint8_t* buffer, uint32_t size)
// Finally, the rest of the nodes in the core dump.
while (StreamHasData(gzipStream)) {
// Test for the end of the stream. The protobuf library gives no way to tell
// the difference between an underlying read error and the stream being
// done. All we can do is attempt to read the size of the next message and
// extrapolate guestimations from the result of that operation.
while (readSizeOfNextMessage(gzipStream, &sizeOfMessage)) {
protobuf::Node node;
if (!parseMessage(gzipStream, node))
if (!parseMessage(gzipStream, sizeOfMessage, node))
return false;
if (NS_WARN_IF(!saveNode(node, edgeReferents)))
return false;

View File

@ -7,6 +7,7 @@ support-files =
[test_notification_basics.html]
[test_notification_storage.html]
[test_bug931307.html]
skip-if = (os == 'android') # Bug 1258975 on android.
[test_notification_resend.html]
skip-if = (buildapp != 'b2g' && buildapp != 'mulet') || e10s # On e10s, faking the app seems to be failing
[test_notification_noresend.html]

View File

@ -211,9 +211,17 @@ public final class IntentHelper implements GeckoEventListener,
callback.sendSuccess(null);
} else {
// We originally loaded about:neterror when we failed to match the Intent. However, many
// websites worked around Chrome's implementation, which does nothing in this case. For
// example, the site might set a timeout and load a play store url for their app if the
// intent link fails to load, i.e. the app is not installed. These work-arounds would often
// end with our users seeing about:neterror instead of the intended experience. While I
// feel showing about:neterror is a better solution for users (when not hacked around),
// we should match the status quo for the good of our users.
//
// Don't log the URI to prevent leaking it.
Log.w(LOGTAG, "Unable to open URI, default case - loading about:neterror");
callback.sendError(getUnknownProtocolErrorPageUri(intent.getData().toString()));
Log.w(LOGTAG, "Unable to open URI - ignoring click");
callback.sendSuccess(null); // pretend we opened the page.
}
}

View File

@ -25,7 +25,7 @@
<layer-list>
<item>
<shape android:shape="rectangle">
<solid android:color="@color/toolbar_grey"/>
<solid android:color="@color/about_page_header_grey"/>
</shape>
</item>

View File

@ -5,6 +5,7 @@
var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/ExtensionContent.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -104,3 +105,9 @@ AboutReaderListener.init();
addMessageListener("RemoteLogins:fillForm", function(message) {
LoginManagerContent.receiveMessage(message, content);
});
ExtensionContent.init(this);
addEventListener("unload", () => {
ExtensionContent.uninit(this);
});

View File

@ -310,6 +310,8 @@
@BINPATH@/components/NetworkGeolocationProvider.manifest
@BINPATH@/components/NetworkGeolocationProvider.js
@BINPATH@/components/extensions.manifest
@BINPATH@/components/utils.manifest
@BINPATH@/components/simpleServices.js
@BINPATH@/components/addonManager.js
@BINPATH@/components/amContentHandler.js
@BINPATH@/components/amInstallTrigger.js

View File

@ -1,5 +1,4 @@
[DEFAULT]
skip-if = os == 'android'
support-files =
file_download.html
file_download.txt
@ -12,3 +11,4 @@ skip-if = 1 # Currently causes too many intermittent failures.
[test_chrome_ext_downloads_search.html]
[test_chrome_ext_eventpage_warning.html]
[test_chrome_ext_contentscript_unrecognizedprop_warning.html]
skip-if = (os == 'android') # browser.tabs is undefined. Bug 1258975 on android.

View File

@ -1,5 +1,5 @@
[DEFAULT]
skip-if = os == 'android' || buildapp == 'mulet'
skip-if = buildapp == 'mulet' || asan
support-files =
head.js
file_WebRequest_page1.html
@ -44,26 +44,26 @@ skip-if = buildapp == 'b2g' # runat != document_idle is not supported.
[test_ext_permission_xhr.html]
skip-if = buildapp == 'b2g' # JavaScript error: jar:remoteopenfile:///data/local/tmp/generated-extension.xpi!/content.js, line 46: NS_ERROR_ILLEGAL_VALUE:
[test_ext_runtime_connect.html]
skip-if = buildapp == 'b2g' # port.sender.tab is undefined on b2g.
skip-if = (os == 'android' || buildapp == 'b2g') # port.sender.tab is undefined on b2g. Bug 1258975 on android.
[test_ext_runtime_connect2.html]
skip-if = buildapp == 'b2g' # port.sender.tab is undefined on b2g.
skip-if = (os == 'android' || buildapp == 'b2g') # port.sender.tab is undefined on b2g. Bug 1258975 on android.
[test_ext_runtime_disconnect.html]
[test_ext_runtime_getPlatformInfo.html]
[test_ext_runtime_sendMessage.html]
[test_ext_sandbox_var.html]
[test_ext_sendmessage_reply.html]
skip-if = buildapp == 'b2g' # sender.tab is undefined on b2g.
skip-if = (os == 'android' || buildapp == 'b2g') # sender.tab is undefined on b2g. Bug 1258975 on android.
[test_ext_sendmessage_reply2.html]
skip-if = buildapp == 'b2g' # sender.tab is undefined on b2g.
skip-if = (os == 'android' || buildapp == 'b2g') # sender.tab is undefined on b2g. Bug 1258975 on android.
[test_ext_sendmessage_doublereply.html]
skip-if = buildapp == 'b2g' # sender.tab is undefined on b2g.
skip-if = (os == 'android' || buildapp == 'b2g') # sender.tab is undefined on b2g. Bug 1258975 on android.
[test_ext_storage.html]
[test_ext_background_runtime_connect_params.html]
[test_ext_cookies.html]
[test_ext_cookies_permissions.html]
skip-if = e10s || buildapp == 'b2g' # Uses cookie service via SpecialPowers.Services, which does not support e10s.
[test_ext_bookmarks.html]
skip-if = buildapp == 'b2g' # unimplemented api.
skip-if = (os == 'android' || buildapp == 'b2g') # unimplemented api. Bug 1258975 on android.
[test_ext_alarms.html]
[test_ext_background_window_properties.html]
[test_ext_background_sub_windows.html]
@ -71,8 +71,10 @@ skip-if = buildapp == 'b2g' # unimplemented api.
[test_ext_jsversion.html]
skip-if = e10s || buildapp == 'b2g' # Uses a console monitor which doesn't work from a content process. The code being tested doesn't run in a tab content process in any case.
[test_ext_i18n.html]
skip-if = (os == 'android') # Bug 1258975 on android.
[test_ext_web_accessible_resources.html]
skip-if = (os == 'android') # Bug 1258975 on android.
[test_ext_webrequest.html]
skip-if = buildapp == 'b2g' # webrequest api uninplemented (bug 1199504)
skip-if = (os == 'android' || buildapp == 'b2g') # webrequest api uninplemented (bug 1199504). Bug 1258975 on android.
[test_ext_webnavigation.html]
skip-if = buildapp == 'b2g' # needs TabManager which is not yet implemented
skip-if = (os == 'android' || buildapp == 'b2g') # needs TabManager which is not yet implemented. Bug 1258975 on android.

View File

@ -19,20 +19,20 @@ support-files =
[test_basic_form_2pw_2.html]
[test_basic_form_autocomplete.html]
skip-if = toolkit == 'android'
skip-if = toolkit == 'android' # Bug 1258975 on android.
[test_bug_627616.html]
skip-if = toolkit == 'android' #TIMED_OUT
skip-if = toolkit == 'android' # Bug 1258975 on android.
[test_master_password.html]
skip-if = toolkit == 'android' #TIMED_OUT
skip-if = toolkit == 'android' # Bug 1258975 on android.
[test_master_password_cleanup.html]
skip-if = toolkit == 'android'
skip-if = toolkit == 'android' # Bug 1258975 on android.
[test_notifications_popup.html]
skip-if = true || os == "linux" || toolkit == 'android' # bug 934057
skip-if = true || os == "linux" || toolkit == 'android' # bug 934057. Bug 1258975 on android.
[test_prompt.html]
skip-if = os == "linux" || toolkit == 'android' #TIMED_OUT
skip-if = os == "linux" || toolkit == 'android' # Bug 1258975 on android.
[test_prompt_async.html]
skip-if = toolkit == 'android' #TIMED_OUT
skip-if = toolkit == 'android' # Bug 1258975 on android.
[test_xhr.html]
skip-if = toolkit == 'android' #TIMED_OUT
skip-if = toolkit == 'android' # Bug 1258975 on android.
[test_xml_load.html]
skip-if = toolkit == 'android' #TIMED_OUT
skip-if = toolkit == 'android' # Bug 1258975 on android.

View File

@ -30,4 +30,5 @@ skip-if = toolkit == 'android' # Bug 1259768
[test_maxlength.html]
[test_passwords_in_type_password.html]
[test_recipe_login_fields.html]
[test_xhr_2.html]
skip-if = (toolkit == 'android') # Bug 1258975 on android.
[test_xhr_2.html]

View File

@ -5587,11 +5587,12 @@
"kind": "enumerated",
"keyed": true,
"n_values": 40,
"description": "Usage of popup notifications, keyed by ID (0 = Offered, 1..4 = Action, 5 = Click outside, 6 = Leave page, 7 = Use 'X', 8 = Not now, 10 = Open submenu, 11 = Learn more. Add 20 if happened after reopen.)"
"description": "(Bug 1207089) Usage of popup notifications, keyed by ID (0 = Offered, 1..4 = Action, 5 = Click outside, 6 = Leave page, 7 = Use 'X', 8 = Not now, 10 = Open submenu, 11 = Learn more. Add 20 if happened after reopen.)"
},
"POPUP_NOTIFICATION_MAIN_ACTION_MS": {
"alert_emails": ["firefox-dev@mozilla.org"],
"expires_in_version": "48",
"bug_numbers": [1207089],
"expires_in_version": "52",
"kind": "exponential",
"keyed": true,
"low": 100,
@ -5601,7 +5602,8 @@
},
"POPUP_NOTIFICATION_DISMISSAL_MS": {
"alert_emails": ["firefox-dev@mozilla.org"],
"expires_in_version": "48",
"bug_numbers": [1207089],
"expires_in_version": "52",
"kind": "exponential",
"keyed": true,
"low": 200,