Merge fx-team to m-c.

This commit is contained in:
Ryan VanderMeulen 2013-11-05 15:13:46 -05:00
commit 62e568a6e1
78 changed files with 1840 additions and 792 deletions

View File

@ -244,6 +244,7 @@ pref("lightweightThemes.update.enabled", true);
// UI tour experience.
pref("browser.uitour.enabled", true);
pref("browser.uitour.requireSecure", true);
pref("browser.uitour.themeOrigin", "https://addons.mozilla.org/%LOCALE%/firefox/themes/");
pref("browser.uitour.pinnedTabUrl", "https://support.mozilla.org/%LOCALE%/kb/pinned-tabs-keep-favorite-websites-open");
pref("browser.uitour.whitelist.add.260", "www.mozilla.org,support.mozilla.org");

View File

@ -146,7 +146,8 @@ let gPage = {
handleEvent: function Page_handleEvent(aEvent) {
switch (aEvent.type) {
case "unload":
this._mutationObserver.disconnect();
if (this._mutationObserver)
this._mutationObserver.disconnect();
gAllPages.unregister(this);
break;
case "click":

View File

@ -15,24 +15,20 @@ function test() {
// Verify that about:preferences tab is displayed when
// browser.preferences.inContent is set to true
Services.prefs.setBoolPref("browser.preferences.inContent", true);
gBrowser.tabContainer.addEventListener("TabOpen", function(aEvent) {
gBrowser.tabContainer.removeEventListener("TabOpen", arguments.callee, true);
let browser = aEvent.originalTarget.linkedBrowser;
browser.addEventListener("load", function(aEvent) {
browser.removeEventListener("load", arguments.callee, true);
is(Services.prefs.getBoolPref("browser.preferences.inContent"), true, "In-content prefs are enabled");
is(browser.contentWindow.location.href, "about:preferences", "Checking if the preferences tab was opened");
gBrowser.removeCurrentTab();
Services.prefs.setBoolPref("browser.preferences.inContent", false);
openPreferences();
}, true);
}, true);
// Open a new tab.
whenNewTabLoaded(window, testPreferences);
}
function testPreferences() {
whenTabLoaded(gBrowser.selectedTab, function () {
is(Services.prefs.getBoolPref("browser.preferences.inContent"), true, "In-content prefs are enabled");
is(content.location.href, "about:preferences", "Checking if the preferences tab was opened");
gBrowser.removeCurrentTab();
Services.prefs.setBoolPref("browser.preferences.inContent", false);
openPreferences();
});
let observer = {
observe: function(aSubject, aTopic, aData) {

View File

@ -237,9 +237,14 @@ function whenNewTabLoaded(aWindow, aCallback) {
return;
}
whenTabLoaded(aWindow.gBrowser.selectedTab, aCallback);
}
function whenTabLoaded(aTab, aCallback) {
let browser = aTab.linkedBrowser;
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
aCallback();
executeSoon(aCallback);
}, true);
}

View File

@ -473,8 +473,37 @@ function openAboutDialog() {
function openPreferences(paneID, extraArgs)
{
if (Services.prefs.getBoolPref("browser.preferences.inContent")) {
openUILinkIn("about:preferences", "tab");
function switchToAdvancedSubPane(doc) {
if (extraArgs && extraArgs["advancedTab"]) {
let advancedPaneTabs = doc.getElementById("advancedPrefs");
advancedPaneTabs.selectedTab = doc.getElementById(extraArgs["advancedTab"]);
}
}
if (getBoolPref("browser.preferences.inContent")) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
if (!win) {
return;
}
let newLoad = !win.switchToTabHavingURI("about:preferences", true);
let browser = win.gBrowser.selectedBrowser;
function switchToPane() {
if (paneID) {
browser.contentWindow.selectCategory(paneID);
}
switchToAdvancedSubPane(browser.contentDocument);
}
if (newLoad) {
browser.addEventListener("load", function onload() {
browser.removeEventListener("load", onload, true);
switchToPane();
}, true);
} else {
switchToPane();
}
} else {
var instantApply = getBoolPref("browser.preferences.instantApply", false);
var features = "chrome,titlebar,toolbar,centerscreen" + (instantApply ? ",dialog=no" : ",modal");
@ -487,16 +516,11 @@ function openPreferences(paneID, extraArgs)
win.document.documentElement.showPane(pane);
}
if (extraArgs && extraArgs["advancedTab"]) {
var advancedPaneTabs = win.document.getElementById("advancedPrefs");
advancedPaneTabs.selectedTab = win.document.getElementById(extraArgs["advancedTab"]);
}
return;
switchToAdvancedSubPane(win.document);
} else {
openDialog("chrome://browser/content/preferences/preferences.xul",
"Preferences", features, paneID, extraArgs);
}
openDialog("chrome://browser/content/preferences/preferences.xul",
"Preferences", features, paneID, extraArgs);
}
}

View File

@ -452,8 +452,10 @@ nsBrowserContentHandler.prototype = {
var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
if (chromeParam) {
// Handle the old preference dialog URL separately (bug 285416)
if (chromeParam == "chrome://browser/content/pref/pref.xul") {
// Handle old preference dialog URLs.
if (chromeParam == "chrome://browser/content/pref/pref.xul" ||
(Services.prefs.getBoolPref("browser.preferences.inContent") &&
chromeParam == "chrome://browser/content/preferences/preferences.xul")) {
openPreferences();
cmdLine.preventDefault = true;
} else try {

View File

@ -2,11 +2,11 @@
* 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/. */
richlistitem {
#handlersView > richlistitem {
-moz-binding: url("chrome://browser/content/preferences/handlers.xml#handler");
}
richlistitem[selected="true"] {
#handlersView > richlistitem[selected="true"] {
-moz-binding: url("chrome://browser/content/preferences/handlers.xml#handler-selected");
}

View File

@ -128,11 +128,6 @@
#endif
<stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
<hbox class="heading" data-category="paneAdvanced" hidden="true">
<image class="preference-icon" type="advanced"/>
<html:h1>&paneAdvanced.title;</html:h1>
</hbox>
<tabbox id="advancedPrefs" flex="1"
data-category="paneAdvanced" hidden="true"
onselect="gAdvancedPane.tabSelectionChanged();">

View File

@ -55,11 +55,6 @@
<key key="&focusSearch2.key;" modifiers="accel" oncommand="gApplicationsPane.focusFilterBox();"/>
</keyset>
<hbox class="heading" data-category="paneApplications" hidden="true">
<image class="preference-icon" type="applications"/>
<html:h1>&paneApplications.title;</html:h1>
</hbox>
<vbox data-category="paneApplications" hidden="true" flex="1">
<hbox>
<textbox id="filter" flex="1"

View File

@ -21,12 +21,9 @@
<script type="application/javascript"
src="chrome://browser/content/preferences/in-content/content.js"/>
<hbox class="heading" data-category="paneContent" hidden="true">
<image class="preference-icon" type="content"/>
<html:h1>&paneContent.title;</html:h1>
</hbox>
<groupbox id="miscGroup" data-category="paneContent" hidden="true">
<caption label="&popups.label;"/>
<grid id="contentGrid">
<columns>
<column flex="1"/>

View File

@ -4,7 +4,6 @@
browser.jar:
content/browser/preferences/in-content/preferences.js
content/browser/preferences/in-content/landing.xul
* content/browser/preferences/in-content/preferences.xul
* content/browser/preferences/in-content/main.xul
* content/browser/preferences/in-content/main.js

View File

@ -1,55 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
- You can obtain one at http://mozilla.org/MPL/2.0/. -->
<vbox data-category="landing">
<html:h1 class="indent-small">&brandShortName;</html:h1>
<hbox id="preferences-home" flex="1">
<button label="&paneGeneral.title;" class="landingButton"
oncommand="gotoPref('paneGeneral');">
<image class="landingButton-icon" type="general"/>
<label class="landingButton-label">&paneGeneral.title;</label>
</button>
<button label="&paneContent.title;" class="landingButton"
oncommand="gotoPref('paneContent');">
<image class="landingButton-icon" type="content"/>
<label class="landingButton-label">&paneContent.title;</label>
</button>
<button label="&paneApplications.title;" class="landingButton"
oncommand="gotoPref('paneApplications');">
<image class="landingButton-icon" type="applications"/>
<label class="landingButton-label">&paneApplications.title;</label>
</button>
<button label="&panePrivacy.title;" class="landingButton"
oncommand="gotoPref('panePrivacy');">
<image class="landingButton-icon" type="privacy"/>
<label class="landingButton-label">&panePrivacy.title;</label>
</button>
<button label="&paneSecurity.title;" class="landingButton"
oncommand="gotoPref('paneSecurity');">
<image class="landingButton-icon" type="security"/>
<label class="landingButton-label">&paneSecurity.title;</label>
</button>
<button label="&paneSync.title;" class="landingButton"
oncommand="gotoPref('paneSync');">
<image class="landingButton-icon" type="sync"/>
<label class="landingButton-label">&paneSync.title;</label>
</button>
<button label="&paneAdvanced.title;" class="landingButton"
oncommand="gotoPref('paneAdvanced');">
<image class="landingButton-icon" type="advanced"/>
<label class="landingButton-label">&paneAdvanced.title;</label>
</button>
</hbox>
</vbox>

View File

@ -85,13 +85,8 @@
#endif
</preferences>
<hbox class="heading" data-category="paneGeneral" hidden="true">
<image class="preference-icon" type="general"/>
<html:h1>&paneGeneral.title;</html:h1>
</hbox>
<!-- Startup -->
<groupbox id="startupGroup" data-category="paneGeneral" hidden="true">
<groupbox id="startupGroup" data-category="paneGeneral">
<caption label="&startup.label;"/>
<hbox align="center">
@ -150,7 +145,7 @@
</groupbox>
<!-- Downloads -->
<groupbox id="downloadsGroup" data-category="paneGeneral" hidden="true">
<groupbox id="downloadsGroup" data-category="paneGeneral">
<caption label="&downloads.label;"/>
<radiogroup id="saveWhere"
@ -189,7 +184,7 @@
</groupbox>
<!-- Tab preferences -->
<groupbox data-category="paneGeneral" hidden="true">
<groupbox data-category="paneGeneral">
<caption label="&tabsGroup.label;"/>
<checkbox id="linkTargeting" label="&newWindowsAsTabs.label;"
accesskey="&newWindowsAsTabs.accesskey;"

View File

@ -12,11 +12,13 @@ const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
addEventListener("DOMContentLoaded", function onLoad() {
removeEventListener("DOMContentLoaded", onLoad);
init_all();
});
function init_all() {
document.documentElement.instantApply = true;
window.history.replaceState("landing", document.title);
window.addEventListener("popstate", onStatePopped, true);
updateCommands();
gMainPane.init();
gPrivacyPane.init();
gAdvancedPane.init();
@ -27,12 +29,32 @@ function init_all() {
var initFinished = document.createEvent("Event");
initFinished.initEvent("Initialized", true, true);
document.dispatchEvent(initFinished);
let categories = document.getElementById("categories");
categories.addEventListener("select", event => gotoPref(event.target.value));
window.addEventListener("popstate", event => selectCategory(event.state));
if (history.length > 1 && history.state) {
updateCommands();
selectCategory(history.state);
} else {
history.replaceState("paneGeneral", document.title);
}
}
function selectCategory(name) {
let categories = document.getElementById("categories");
let item = categories.querySelector(".category[value=" + name + "]");
categories.selectedItem = item;
}
function gotoPref(page) {
search(page, "data-category");
window.history.pushState(page, document.title);
if (history.state != page) {
window.history.pushState(page, document.title);
}
updateCommands();
search(page, "data-category");
}
function cmd_back() {
@ -43,11 +65,6 @@ function cmd_forward() {
window.history.forward();
}
function onStatePopped(aEvent) {
updateCommands();
search(aEvent.state, "data-category");
}
function updateCommands() {
document.getElementById("back-btn").disabled = !canGoBack();
document.getElementById("forward-btn").disabled = !canGoForward();

View File

@ -54,8 +54,7 @@
#define USE_WIN_TITLE_STYLE
#endif
<page onload="init_all();"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
#ifdef USE_WIN_TITLE_STYLE
title="&prefWindow.titleWin;">
@ -86,10 +85,58 @@
oncommand="cmd_forward()" tooltiptext="&buttonForward.tooltip;"
disabled="true"/>
</hbox>
<hbox class="main-content" flex="1">
<prefpane flex="1" id="mainPrefPane">
#include landing.xul
<hbox flex="1">
<!-- category list -->
<richlistbox id="categories">
<richlistitem id="category-general" class="category" align="center"
value="paneGeneral" tooltiptext="&paneGeneral.title;">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneGeneral.title;"/>
</richlistitem>
<richlistitem id="category-content" class="category" align="center"
value="paneContent" tooltiptext="&paneContent.title;">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneContent.title;"/>
</richlistitem>
<richlistitem id="category-application" class="category" align="center"
value="paneApplications" tooltiptext="&paneApplications.title;">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneApplications.title;"/>
</richlistitem>
<richlistitem id="category-privacy" class="category" align="center"
value="panePrivacy" tooltiptext="&panePrivacy.title;">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&panePrivacy.title;"/>
</richlistitem>
<richlistitem id="category-security" class="category" align="center"
value="paneSecurity" tooltiptext="&paneSecurity.title;">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneSecurity.title;"/>
</richlistitem>
#ifdef MOZ_SERVICES_SYNC
<richlistitem id="category-sync" class="category" align="center"
value="paneSync" tooltiptext="&paneSync.title;">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneSync.title;"/>
</richlistitem>
#endif
<richlistitem id="category-advanced" class="category" align="center"
value="paneAdvanced" tooltiptext="&paneAdvanced.title;">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneAdvanced.title;"/>
</richlistitem>
</richlistbox>
<box class="main-content" flex="1">
<prefpane flex="1" id="mainPrefPane">
#include main.xul
#include privacy.xul
#include advanced.xul
@ -99,7 +146,8 @@
#ifdef MOZ_SERVICES_SYNC
#include sync.xul
#endif
</prefpane>
</prefpane>
</box>
</hbox>
</page>

View File

@ -65,11 +65,6 @@
</preferences>
<hbox class="heading" data-category="panePrivacy" hidden="true">
<image class="preference-icon" type="privacy"/>
<html:h1>&panePrivacy.title;</html:h1>
</hbox>
<!-- Tracking -->
<groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" align="start">
<caption label="&tracking.label;"/>

View File

@ -30,13 +30,10 @@
</preferences>
<hbox class="heading" data-category="paneSecurity" hidden="true">
<image class="preference-icon" type="security"/>
<html:h1>&paneSecurity.title;</html:h1>
</hbox>
<!-- addons, forgery (phishing) UI -->
<groupbox id="addonsPhishingGroup" data-category="paneSecurity" hidden="true">
<caption label="&general.label;"/>
<hbox id="addonInstallBox">
<checkbox id="warnAddonInstall" flex="1"
label="&warnAddonInstall.label;"

View File

@ -28,11 +28,6 @@
<script type="application/javascript"
src="chrome://browser/content/sync/utils.js"/>
<hbox class="heading" data-category="paneSync" hidden="true">
<image class="preference-icon" type="sync"/>
<html:h1>&paneSync.title;</html:h1>
</hbox>
<deck id="weavePrefsDeck" data-category="paneSync" hidden="true">
<vbox id="noAccount" align="center">
<spacer flex="1"/>

View File

@ -149,7 +149,7 @@ AppValidator.prototype.validateLaunchPath = function (manifest) {
try {
indexURL = Services.io.newURI(path, null, Services.io.newURI(origin, null, null)).spec;
} catch(e) {
this.error(strings.formatStringFromName("validator.invalidLaunchPath", [origin + path], 1));
this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [origin + path], 1));
deferred.resolve();
return deferred.promise;
}
@ -158,25 +158,25 @@ AppValidator.prototype.validateLaunchPath = function (manifest) {
try {
req.open("HEAD", indexURL, true);
} catch(e) {
this.error(strings.formatStringFromName("validator.invalidLaunchPath", [indexURL], 1));
this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
deferred.resolve();
return deferred.promise;
}
req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
req.onload = () => {
if (req.status >= 400)
this.error(strings.formatStringFromName("validator.invalidLaunchPathBadHttpCode", [indexURL, req.status], 2));
this.error(strings.formatStringFromName("validator.accessFailedLaunchPathBadHttpCode", [indexURL, req.status], 2));
deferred.resolve();
};
req.onerror = () => {
this.error(strings.formatStringFromName("validator.invalidLaunchPath", [indexURL], 1));
this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
deferred.resolve();
};
try {
req.send(null);
} catch(e) {
this.error(strings.formatStringFromName("validator.invalidLaunchPath", [indexURL], 1));
this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
deferred.resolve();
}

View File

@ -99,7 +99,7 @@
let validator = createHosted("wrong-launch-path");
validator.validate().then(() => {
is(validator.errors.length, 1, "app with non-existant launch path got an error");
is(validator.errors[0], strings.formatStringFromName("validator.invalidLaunchPathBadHttpCode", [origin + "wrong-path.html", 404], 2),
is(validator.errors[0], strings.formatStringFromName("validator.accessFailedLaunchPathBadHttpCode", [origin + "wrong-path.html", 404], 2),
"with the right error message");
is(validator.warnings.length, 0, "but no warning");
next();
@ -112,7 +112,7 @@
let file = nsFile(validator.project.location);
file.append("wrong-path.html");
let url = Services.io.newFileURI(file);
is(validator.errors[0], strings.formatStringFromName("validator.invalidLaunchPath", [url.spec], 1),
is(validator.errors[0], strings.formatStringFromName("validator.accessFailedLaunchPath", [url.spec], 1),
"with the expected message");
is(validator.warnings.length, 0, "but no warning");

View File

@ -358,6 +358,9 @@ InspectorPanel.prototype = {
this._initMarkup();
this.once("markuploaded", () => {
if (this._destroyPromise) {
return;
}
this.markup.expandNode(this.selection.nodeFront);
this.setupSearchBox();
this.emit("new-root");

View File

@ -14,6 +14,7 @@ const COLLAPSE_ATTRIBUTE_LENGTH = 120;
const COLLAPSE_DATA_URL_REGEX = /^data.+base64/;
const COLLAPSE_DATA_URL_LENGTH = 60;
const CONTAINER_FLASHING_DURATION = 500;
const IMAGE_PREVIEW_MAX_DIM = 400;
const {UndoStack} = require("devtools/shared/undo");
const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
@ -99,6 +100,10 @@ function MarkupView(aInspector, aFrame, aControllerWindow) {
gDevTools.on("pref-changed", this._handlePrefChange);
this._initPreview();
this.tooltip = new Tooltip(this._inspector.panelDoc);
this.tooltip.startTogglingOnHover(this._elt,
this._buildTooltipContent.bind(this));
}
exports.MarkupView = MarkupView;
@ -148,6 +153,25 @@ MarkupView.prototype = {
updateChildren(documentElement);
},
_buildTooltipContent: function(target) {
// From the target passed here, let's find the parent MarkupContainer
// and ask it if the tooltip should be shown
let parent = target, container;
while (parent !== this.doc.body) {
if (parent.container) {
container = parent.container;
break;
}
parent = parent.parentNode;
}
if (container) {
// With the newly found container, delegate the tooltip content creation
// and decision to show or not the tooltip
return container._buildTooltipContent(target, this.tooltip);
}
},
/**
* Highlight the inspector selected node.
*/
@ -954,6 +978,9 @@ MarkupView.prototype = {
container.destroy();
}
delete this._containers;
this.tooltip.destroy();
delete this.tooltip;
},
/**
@ -1099,8 +1126,8 @@ function MarkupContainer(aMarkupView, aNode, aInspector) {
this._onMouseDown = this._onMouseDown.bind(this);
this.elt.addEventListener("mousedown", this._onMouseDown, false);
this.tooltip = null;
this._attachTooltipIfNeeded();
// Prepare the image preview tooltip data if any
this._prepareImagePreview();
}
MarkupContainer.prototype = {
@ -1108,36 +1135,43 @@ MarkupContainer.prototype = {
return "[MarkupContainer for " + this.node + "]";
},
_attachTooltipIfNeeded: function() {
_prepareImagePreview: function() {
if (this.node.tagName) {
let tagName = this.node.tagName.toLowerCase();
let isImage = tagName === "img" &&
this.editor.getAttributeElement("src");
let isCanvas = tagName && tagName === "canvas";
let srcAttr = this.editor.getAttributeElement("src");
let isImage = tagName === "img" && srcAttr;
let isCanvas = tagName === "canvas";
// Get the image data for later so that when the user actually hovers over
// the element, the tooltip does contain the image
if (isImage || isCanvas) {
this.tooltip = new Tooltip(this._inspector.panelDoc);
let def = promise.defer();
this.node.getImageData().then(data => {
this.tooltipData = {
target: isImage ? srcAttr : this.editor.tag,
data: def.promise
};
this.node.getImageData(IMAGE_PREVIEW_MAX_DIM).then(data => {
if (data) {
data.string().then(str => {
this.tooltip.setImageContent(str);
data.data.string().then(str => {
// Resolving the data promise and, to always keep tooltipData.data
// as a promise, create a new one that resolves immediately
def.resolve(str, data.size);
this.tooltipData.data = promise.resolve(str, data.size);
});
}
});
}
}
},
// If it's an image, show the tooltip on the src attribute
if (isImage) {
this.tooltip.startTogglingOnHover(this.editor.getAttributeElement("src"));
}
// If it's a canvas, show it on the tag
if (isCanvas) {
this.tooltip.startTogglingOnHover(this.editor.tag);
}
_buildTooltipContent: function(target, tooltip) {
if (this.tooltipData && target === this.tooltipData.target) {
this.tooltipData.data.then((data, size) => {
tooltip.setImageContent(data, size);
});
return true;
}
},
@ -1375,12 +1409,6 @@ MarkupContainer.prototype = {
// Destroy my editor
this.editor.destroy();
// Destroy the tooltip if any
if (this.tooltip) {
this.tooltip.destroy();
this.tooltip = null;
}
}
};

View File

@ -88,14 +88,14 @@ function testImageTooltip(index) {
target = container.editor.getAttributeElement("src");
}
assertTooltipShownOn(container.tooltip, target, () => {
let images = container.tooltip.panel.getElementsByTagName("image");
assertTooltipShownOn(target, () => {
let images = markup.tooltip.panel.getElementsByTagName("image");
is(images.length, 1, "Tooltip for [" + TEST_NODES[index] + "] contains an image");
if (isImg) {
compareImageData(node, images[0].src);
}
container.tooltip.hide();
markup.tooltip.hide();
testImageTooltip(index + 1);
});
@ -115,17 +115,17 @@ function compareImageData(img, imgData) {
is(data, imgData, "Tooltip image has the right content");
}
function assertTooltipShownOn(tooltip, element, cb) {
function assertTooltipShownOn(element, cb) {
// If there is indeed a show-on-hover on element, the xul panel will be shown
tooltip.panel.addEventListener("popupshown", function shown() {
tooltip.panel.removeEventListener("popupshown", shown, true);
markup.tooltip.panel.addEventListener("popupshown", function shown() {
markup.tooltip.panel.removeEventListener("popupshown", shown, true);
// Poll until the image gets loaded in the tooltip. This is required because
// markup containers only load images in their associated tooltips when
// the image data comes back from the server. However, this test is executed
// synchronously as soon as "inspector-updated" is fired, which is before
// the data for images is known.
let hasImage = () => tooltip.panel.getElementsByTagName("image").length;
let hasImage = () => markup.tooltip.panel.getElementsByTagName("image").length;
let poll = setInterval(() => {
if (hasImage()) {
clearInterval(poll);
@ -133,5 +133,5 @@ function assertTooltipShownOn(tooltip, element, cb) {
}
}, 200);
}, true);
tooltip._showOnHover(element);
markup.tooltip._showOnHover(element);
}

View File

@ -159,7 +159,7 @@ var Scratchpad = {
{
this._dirty = aValue;
if (!aValue && this.editor)
this.editor.markClean();
this.editor.setClean();
this._updateTitle();
},

View File

@ -15,6 +15,7 @@ Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
const promise = require("sdk/core/promise");
const EventEmitter = require("devtools/shared/event-emitter");
const {Tooltip} = require("devtools/shared/widgets/Tooltip");
const Editor = require("devtools/sourceeditor/editor");
// The panel's window global is an EventEmitter firing the following events:
@ -31,12 +32,14 @@ const EVENTS = {
};
const STRINGS_URI = "chrome://browser/locale/devtools/shadereditor.properties"
const HIGHLIGHT_COLOR = [1, 0, 0, 1];
const BLACKBOX_COLOR = [0, 0, 0, 0];
const TYPING_MAX_DELAY = 500;
const HIGHLIGHT_COLOR = [1, 0, 0, 1]; // rgba
const TYPING_MAX_DELAY = 500; // ms
const SHADERS_AUTOGROW_ITEMS = 4;
const GUTTER_ERROR_PANEL_OFFSET_X = 7; // px
const GUTTER_ERROR_PANEL_DELAY = 100; // ms
const DEFAULT_EDITOR_CONFIG = {
mode: Editor.modes.text,
gutters: ["errors"],
lineNumbers: true,
showAnnotationRuler: true
};
@ -174,25 +177,25 @@ let ShadersListView = Heritage.extend(WidgetMethods, {
showItemCheckboxes: true
});
this._onShaderSelect = this._onShaderSelect.bind(this);
this._onShaderCheck = this._onShaderCheck.bind(this);
this._onShaderMouseEnter = this._onShaderMouseEnter.bind(this);
this._onShaderMouseLeave = this._onShaderMouseLeave.bind(this);
this._onProgramSelect = this._onProgramSelect.bind(this);
this._onProgramCheck = this._onProgramCheck.bind(this);
this._onProgramMouseEnter = this._onProgramMouseEnter.bind(this);
this._onProgramMouseLeave = this._onProgramMouseLeave.bind(this);
this.widget.addEventListener("select", this._onShaderSelect, false);
this.widget.addEventListener("check", this._onShaderCheck, false);
this.widget.addEventListener("mouseenter", this._onShaderMouseEnter, true);
this.widget.addEventListener("mouseleave", this._onShaderMouseLeave, true);
this.widget.addEventListener("select", this._onProgramSelect, false);
this.widget.addEventListener("check", this._onProgramCheck, false);
this.widget.addEventListener("mouseenter", this._onProgramMouseEnter, true);
this.widget.addEventListener("mouseleave", this._onProgramMouseLeave, true);
},
/**
* Destruction function, called when the tool is closed.
*/
destroy: function() {
this.widget.removeEventListener("select", this._onShaderSelect, false);
this.widget.removeEventListener("check", this._onShaderCheck, false);
this.widget.removeEventListener("mouseenter", this._onShaderMouseEnter, true);
this.widget.removeEventListener("mouseleave", this._onShaderMouseLeave, true);
this.widget.removeEventListener("select", this._onProgramSelect, false);
this.widget.removeEventListener("check", this._onProgramCheck, false);
this.widget.removeEventListener("mouseenter", this._onProgramMouseEnter, true);
this.widget.removeEventListener("mouseleave", this._onProgramMouseLeave, true);
},
/**
@ -248,9 +251,9 @@ let ShadersListView = Heritage.extend(WidgetMethods, {
},
/**
* The select listener for the sources container.
* The select listener for the programs container.
*/
_onShaderSelect: function({ detail: sourceItem }) {
_onProgramSelect: function({ detail: sourceItem }) {
if (!sourceItem) {
return;
}
@ -280,19 +283,19 @@ let ShadersListView = Heritage.extend(WidgetMethods, {
},
/**
* The check listener for the sources container.
* The check listener for the programs container.
*/
_onShaderCheck: function({ detail: { checked }, target }) {
_onProgramCheck: function({ detail: { checked }, target }) {
let sourceItem = this.getItemForElement(target);
let attachment = sourceItem.attachment;
attachment.isBlackBoxed = !checked;
attachment.programActor[checked ? "unhighlight" : "highlight"](BLACKBOX_COLOR);
attachment.programActor[checked ? "unblackbox" : "blackbox"]();
},
/**
* The mouseenter listener for the sources container.
* The mouseenter listener for the programs container.
*/
_onShaderMouseEnter: function(e) {
_onProgramMouseEnter: function(e) {
let sourceItem = this.getItemForElement(e.target, { noSiblings: true });
if (sourceItem && !sourceItem.attachment.isBlackBoxed) {
sourceItem.attachment.programActor.highlight(HIGHLIGHT_COLOR);
@ -305,9 +308,9 @@ let ShadersListView = Heritage.extend(WidgetMethods, {
},
/**
* The mouseleave listener for the sources container.
* The mouseleave listener for the programs container.
*/
_onShaderMouseLeave: function(e) {
_onProgramMouseLeave: function(e) {
let sourceItem = this.getItemForElement(e.target, { noSiblings: true });
if (sourceItem && !sourceItem.attachment.isBlackBoxed) {
sourceItem.attachment.programActor.unhighlight();
@ -427,6 +430,9 @@ let ShadersEditorsView = {
*/
_onChanged: function(type) {
setNamedTimeout("gl-typed", TYPING_MAX_DELAY, () => this._doCompile(type));
// Remove all the gutter markers and line classes from the editor.
this._cleanEditor(type);
},
/**
@ -443,13 +449,117 @@ let ShadersEditorsView = {
try {
yield shaderActor.compile(editor.getText());
window.emit(EVENTS.SHADER_COMPILED, null);
// TODO: remove error gutter markers, after bug 919709 lands.
} catch (error) {
window.emit(EVENTS.SHADER_COMPILED, error);
// TODO: add error gutter markers, after bug 919709 lands.
this._onSuccessfulCompilation();
} catch (e) {
this._onFailedCompilation(type, editor, e);
}
}.bind(this));
},
/**
* Called uppon a successful shader compilation.
*/
_onSuccessfulCompilation: function() {
// Signal that the shader was compiled successfully.
window.emit(EVENTS.SHADER_COMPILED, null);
},
/**
* Called uppon an unsuccessful shader compilation.
*/
_onFailedCompilation: function(type, editor, errors) {
let lineCount = editor.lineCount();
let currentLine = editor.getCursor().line;
let listeners = { mouseenter: this._onMarkerMouseEnter };
function matchLinesAndMessages(string) {
return {
// First number that is not equal to 0.
lineMatch: string.match(/\d{2,}|[1-9]/),
// The string after all the numbers, semicolons and spaces.
textMatch: string.match(/[^\s\d:][^\r\n|]*/)
};
}
function discardInvalidMatches(e) {
// Discard empty line and text matches.
return e.lineMatch && e.textMatch;
}
function sanitizeValidMatches(e) {
return {
// Drivers might yield retarded line numbers under some obscure
// circumstances. Don't throw the errors away in those cases,
// just display them on the currently edited line.
line: e.lineMatch[0] > lineCount ? currentLine : e.lineMatch[0] - 1,
// Trim whitespace from the beginning and the end of the message,
// and replace all other occurences of double spaces to a single space.
text: e.textMatch[0].trim().replace(/\s{2,}/g, " ")
};
}
function sortByLine(first, second) {
// Sort all the errors ascending by their corresponding line number.
return first.line > second.line ? 1 : -1;
}
function groupSameLineMessages(accumulator, current) {
// Group errors corresponding to the same line number to a single object.
let previous = accumulator[accumulator.length - 1];
if (!previous || previous.line != current.line) {
return [...accumulator, {
line: current.line,
messages: [current.text]
}];
} else {
previous.messages.push(current.text);
return accumulator;
}
}
function displayErrors({ line, messages }) {
// Add gutter markers and line classes for every error in the source.
editor.addMarker(line, "errors", "error");
editor.setMarkerListeners(line, "errors", "error", listeners, messages);
editor.addLineClass(line, "error-line");
}
(this._errors[type] = errors.link
.split("ERROR")
.map(matchLinesAndMessages)
.filter(discardInvalidMatches)
.map(sanitizeValidMatches)
.sort(sortByLine)
.reduce(groupSameLineMessages, []))
.forEach(displayErrors);
// Signal that the shader wasn't compiled successfully.
window.emit(EVENTS.SHADER_COMPILED, errors);
},
/**
* Event listener for the 'mouseenter' event on a marker in the editor gutter.
*/
_onMarkerMouseEnter: function(line, node, messages) {
if (node._markerErrorsTooltip) {
return;
}
let tooltip = node._markerErrorsTooltip = new Tooltip(document);
tooltip.defaultOffsetX = GUTTER_ERROR_PANEL_OFFSET_X;
tooltip.setTextContent.apply(tooltip, messages);
tooltip.startTogglingOnHover(node, () => true, GUTTER_ERROR_PANEL_DELAY);
},
/**
* Removes all the gutter markers and line classes from the editor.
*/
_cleanEditor: function(type) {
this._getEditor(type).then(editor => {
editor.removeAllMarkers("errors");
this._errors[type].forEach(e => editor.removeLineClass(e.line));
this._errors[type].length = 0;
});
},
_errors: {
vs: [],
fs: []
}
};

View File

@ -1,6 +1,7 @@
[DEFAULT]
support-files =
doc_multiple-contexts.html
doc_overlapping-geometry.html
doc_shader-order.html
doc_simple-canvas.html
head.js
@ -8,6 +9,8 @@ support-files =
[browser_se_aaa_run_first_leaktest.js]
[browser_se_bfcache.js]
[browser_se_editors-contents.js]
[browser_se_editors-error-gutter.js]
[browser_se_editors-error-tooltip.js]
[browser_se_editors-lazy-init.js]
[browser_se_first-run.js]
[browser_se_navigation.js]
@ -34,3 +37,4 @@ support-files =
[browser_webgl-actor-test-14.js]
[browser_webgl-actor-test-15.js]
[browser_webgl-actor-test-16.js]
[browser_webgl-actor-test-17.js]

View File

@ -0,0 +1,156 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if error indicators are shown in the editor's gutter and text area
* when there's a shader compilation error.
*/
function ifWebGLSupported() {
let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
reload(target);
yield once(gFront, "program-linked");
let vsEditor = yield ShadersEditorsView._getEditor("vs");
let fsEditor = yield ShadersEditorsView._getEditor("fs");
vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
info("Error marks added in the vertex shader editor.");
vsEditor.insertText(" ", { line: 1, ch: 0 });
is(vsEditor.getText(1), " precision lowp float;", "Typed space.");
checkHasVertFirstError(false, vertError);
checkHasVertSecondError(false, vertError);
info("Error marks removed while typing in the vertex shader editor.");
let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
info("Error marks were re-added after recompiling the vertex shader.");
fsEditor.replaceText("vec4", { line: 2, ch: 14 }, { line: 2, ch: 18 });
let fragError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
checkHasFragError(true, fragError);
info("Error marks added in the fragment shader editor.");
fsEditor.insertText(" ", { line: 1, ch: 0 });
is(fsEditor.getText(1), " precision lowp float;", "Typed space.");
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
checkHasFragError(false, fragError);
info("Error marks removed while typing in the fragment shader editor.");
let fragError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
checkHasFragError(true, fragError);
info("Error marks were re-added after recompiling the fragment shader.");
vsEditor.replaceText("2", { line: 3, ch: 19 }, { line: 3, ch: 20 });
checkHasVertFirstError(false, vertError);
checkHasVertSecondError(false, vertError);
checkHasFragError(true, fragError);
info("Error marks removed while typing in the vertex shader editor again.");
let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(true, vertError);
checkHasFragError(true, fragError);
info("Error marks were re-added after recompiling the fragment shader again.");
yield teardown(panel);
finish();
function checkHasVertFirstError(bool, error) {
ok(error, "Vertex shader compiled with errors.");
isnot(error.link, "", "The linkage status should not be empty.");
let line = 7;
info("Checking first vertex shader error on line " + line + "...");
is(vsEditor.hasMarker(line, "errors", "error"), bool,
"Error is " + (bool ? "" : "not ") + "shown in the editor's gutter.");
is(vsEditor.hasLineClass(line, "error-line"), bool,
"Error style is " + (bool ? "" : "not ") + "applied to the faulty line.");
let parsed = ShadersEditorsView._errors.vs;
is(parsed.length >= 1, bool,
"There's " + (bool ? ">= 1" : "< 1") + " parsed vertex shader error(s).");
if (bool) {
is(parsed[0].line, line,
"The correct line was parsed.");
is(parsed[0].messages.length, 2,
"There are 2 parsed messages.");
ok(parsed[0].messages[0].contains("'constructor' : too many arguments"),
"The correct first message was parsed.");
ok(parsed[0].messages[1].contains("'assign' : cannot convert from"),
"The correct second message was parsed.");
}
}
function checkHasVertSecondError(bool, error) {
ok(error, "Vertex shader compiled with errors.");
isnot(error.link, "", "The linkage status should not be empty.");
let line = 8;
info("Checking second vertex shader error on line " + line + "...");
is(vsEditor.hasMarker(line, "errors", "error"), bool,
"Error is " + (bool ? "" : "not ") + "shown in the editor's gutter.");
is(vsEditor.hasLineClass(line, "error-line"), bool,
"Error style is " + (bool ? "" : "not ") + "applied to the faulty line.");
let parsed = ShadersEditorsView._errors.vs;
is(parsed.length >= 2, bool,
"There's " + (bool ? ">= 2" : "< 2") + " parsed vertex shader error(s).");
if (bool) {
is(parsed[1].line, line,
"The correct line was parsed.");
is(parsed[1].messages.length, 1,
"There is 1 parsed message.");
ok(parsed[1].messages[0].contains("'assign' : cannot convert from"),
"The correct message was parsed.");
}
}
function checkHasFragError(bool, error) {
ok(error, "Fragment shader compiled with errors.");
isnot(error.link, "", "The linkage status should not be empty.");
let line = 5;
info("Checking first vertex shader error on line " + line + "...");
is(fsEditor.hasMarker(line, "errors", "error"), bool,
"Error is " + (bool ? "" : "not ") + "shown in the editor's gutter.");
is(fsEditor.hasLineClass(line, "error-line"), bool,
"Error style is " + (bool ? "" : "not ") + "applied to the faulty line.");
let parsed = ShadersEditorsView._errors.fs;
is(parsed.length >= 1, bool,
"There's " + (bool ? ">= 2" : "< 1") + " parsed fragment shader error(s).");
if (bool) {
is(parsed[0].line, line,
"The correct line was parsed.");
is(parsed[0].messages.length, 1,
"There is 1 parsed message.");
ok(parsed[0].messages[0].contains("'constructor' : too many arguments"),
"The correct message was parsed.");
}
}
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, (aName, aData) => deferred.resolve(aData));
return deferred.promise;
}

View File

@ -0,0 +1,59 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if error tooltips can be opened from the editor's gutter when there's
* a shader compilation error.
*/
function ifWebGLSupported() {
let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
reload(target);
yield once(gFront, "program-linked");
let vsEditor = yield ShadersEditorsView._getEditor("vs");
let fsEditor = yield ShadersEditorsView._getEditor("fs");
vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
// Synthesizing 'mouseenter' events doesn't work, hack around this by
// manually calling the event listener with the expected arguments.
let editorDocument = vsEditor.container.contentDocument;
let marker = editorDocument.querySelector(".error");
let parsed = ShadersEditorsView._errors.vs[0].messages;
ShadersEditorsView._onMarkerMouseEnter(7, marker, parsed);
let tooltip = marker._markerErrorsTooltip;
ok(tooltip, "A tooltip was created successfully.");
let content = tooltip.content;
ok(tooltip.content,
"Some tooltip's content was set.");
is(tooltip.content.className, "devtools-tooltip-simple-text-container",
"The tooltip's content container was created correctly.");
let messages = content.childNodes;
is(messages.length, 2,
"There are two messages displayed in the tooltip.");
is(messages[0].className, "devtools-tooltip-simple-text",
"The first message was created correctly.");
is(messages[1].className, "devtools-tooltip-simple-text",
"The second message was created correctly.");
ok(messages[0].textContent.contains("'constructor' : too many arguments"),
"The first message contains the correct text.");
ok(messages[1].textContent.contains("'assign' : cannot convert"),
"The second message contains the correct text.");
yield teardown(panel);
finish();
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, (aName, aData) => deferred.resolve(aData));
return deferred.promise;
}

View File

@ -55,9 +55,9 @@ function ifWebGLSupported() {
is(getBlackBoxCheckbox(panel, 1).checked, true,
"The second blackbox checkbox should still be checked.");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The first program was correctly blackboxed.");
@ -72,35 +72,35 @@ function ifWebGLSupported() {
is(getBlackBoxCheckbox(panel, 1).checked, false,
"The second blackbox checkbox should now be unchecked.");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
ok(true, "The second program was correctly blackboxed.");
ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 0) });
ShadersListView._onProgramMouseEnter({ target: getItemLabel(panel, 0) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
ok(true, "Highlighting didn't work while blackboxed (1).");
ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 0) });
ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 1) });
ShadersListView._onProgramMouseLeave({ target: getItemLabel(panel, 0) });
ShadersListView._onProgramMouseEnter({ target: getItemLabel(panel, 1) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
ok(true, "Highlighting didn't work while blackboxed (2).");
ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 1) });
ShadersListView._onProgramMouseLeave({ target: getItemLabel(panel, 1) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
ok(true, "Highlighting didn't work while blackboxed (3).");
getBlackBoxCheckbox(panel, 0).click();
@ -121,7 +121,7 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The two programs were correctly unblackboxed.");
ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 0) });
ShadersListView._onProgramMouseEnter({ target: getItemLabel(panel, 0) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
@ -129,8 +129,8 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The first program was correctly highlighted.");
ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 0) });
ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 1) });
ShadersListView._onProgramMouseLeave({ target: getItemLabel(panel, 0) });
ShadersListView._onProgramMouseEnter({ target: getItemLabel(panel, 1) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2");
@ -138,7 +138,7 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2");
ok(true, "The second program was correctly highlighted.");
ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 1) });
ShadersListView._onProgramMouseLeave({ target: getItemLabel(panel, 1) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");

View File

@ -35,7 +35,7 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 0) });
ShadersListView._onProgramMouseEnter({ target: getItemLabel(panel, 0) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
@ -43,8 +43,8 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The first program was correctly highlighted.");
ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 0) });
ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 1) });
ShadersListView._onProgramMouseLeave({ target: getItemLabel(panel, 0) });
ShadersListView._onProgramMouseEnter({ target: getItemLabel(panel, 1) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2");
@ -52,7 +52,7 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2");
ok(true, "The second program was correctly highlighted.");
ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 1) });
ShadersListView._onProgramMouseLeave({ target: getItemLabel(panel, 1) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
@ -60,7 +60,7 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The two programs were correctly unhighlighted.");
ShadersListView._onShaderMouseEnter({ target: getBlackBoxCheckbox(panel, 0) });
ShadersListView._onProgramMouseEnter({ target: getBlackBoxCheckbox(panel, 0) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
@ -68,7 +68,7 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The two programs were left unchanged after hovering a blackbox checkbox.");
ShadersListView._onShaderMouseLeave({ target: getBlackBoxCheckbox(panel, 0) });
ShadersListView._onProgramMouseLeave({ target: getBlackBoxCheckbox(panel, 0) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");

View File

@ -2,7 +2,8 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if editing a vertex and a fragment shader works properly.
* Tests if editing a vertex and a fragment shader would permanently store
* their new source on the backend and reshow it in the frontend when required.
*/
function ifWebGLSupported() {

View File

@ -2,8 +2,8 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the highlight/unhighlight operations on program actors
* work as expected.
* Tests that the highlight/unhighlight and blackbox/unblackbox operations on
* program actors work as expected.
*/
function ifWebGLSupported() {
@ -17,19 +17,31 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
yield checkShaderSource("The shader sources are correct before highlighting.");
ok(true, "The top left pixel color was correct before highlighting.");
ok(true, "The corner pixel colors are correct before highlighting.");
yield programActor.highlight([0, 0, 1, 1]);
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 255, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 255, a: 255 }, true);
yield checkShaderSource("The shader sources are preserved after highlighting.");
ok(true, "The top left pixel color is correct after highlighting.");
ok(true, "The corner pixel colors are correct after highlighting.");
yield programActor.unhighlight();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
yield checkShaderSource("The shader sources are correct after unhighlighting.");
ok(true, "The top left pixel color is correct after unhighlighting.");
ok(true, "The corner pixel colors are correct after unhighlighting.");
yield programActor.blackbox();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 0, a: 255 }, true);
yield checkShaderSource("The shader sources are preserved after blackboxing.");
ok(true, "The corner pixel colors are correct after blackboxing.");
yield programActor.unblackbox();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
yield checkShaderSource("The shader sources are correct after unblackboxing.");
ok(true, "The corner pixel colors are correct after unblackboxing.");
function checkShaderSource(aMessage) {
return Task.spawn(function() {

View File

@ -0,0 +1,47 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the blackbox/unblackbox operations work as expected with
* overlapping geometry.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(OVERLAPPING_GEOMETRY_CANVAS_URL);
front.setup({ reload: true });
let firstProgramActor = yield once(front, "program-linked");
let secondProgramActor = yield once(front, "program-linked");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true);
ok(true, "The corner vs. center pixel colors are correct before blackboxing.");
yield firstProgramActor.blackbox();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true);
ok(true, "The corner vs. center pixel colors are correct after blackboxing (1).");
yield firstProgramActor.unblackbox();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true);
ok(true, "The corner vs. center pixel colors are correct after unblackboxing (1).");
yield secondProgramActor.blackbox();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 255, g: 255, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true);
ok(true, "The corner vs. center pixel colors are correct after blackboxing (2).");
yield secondProgramActor.unblackbox();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true);
ok(true, "The corner vs. center pixel colors are correct after unblackboxing (2).");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,120 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>WebGL editor test page</title>
<script id="shader-vs" type="x-shader/x-vertex">
precision lowp float;
attribute vec3 aVertexPosition;
uniform float uDepth;
void main(void) {
gl_Position = vec4(aVertexPosition, uDepth);
}
</script>
<script id="shader-fs-0" type="x-shader/x-fragment">
precision lowp float;
void main(void) {
gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
</script>
<script id="shader-fs-1" type="x-shader/x-fragment">
precision lowp float;
void main(void) {
gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0);
}
</script>
</head>
<body>
<canvas id="canvas" width="128" height="128"></canvas>
<script type="text/javascript;version=1.8">
"use strict";
let canvas, gl;
let program = [];
let squareVerticesPositionBuffer;
let vertexPositionAttribute = [];
let depthUniform = [];
window.onload = function() {
canvas = document.querySelector("canvas");
gl = canvas.getContext("webgl");
gl.clearColor(0.0, 0.0, 0.0, 1.0);
initProgram(0);
initProgram(1);
initBuffers();
drawScene();
}
function initProgram(i) {
let vertexShader = getShader("shader-vs");
let fragmentShader = getShader("shader-fs-" + i);
program[i] = gl.createProgram();
gl.attachShader(program[i], vertexShader);
gl.attachShader(program[i], fragmentShader);
gl.linkProgram(program[i]);
vertexPositionAttribute[i] = gl.getAttribLocation(program[i], "aVertexPosition");
gl.enableVertexAttribArray(vertexPositionAttribute[i]);
depthUniform[i] = gl.getUniformLocation(program[i], "uDepth");
}
function getShader(id) {
let script = document.getElementById(id);
let source = script.textContent;
let shader;
if (script.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (script.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
}
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
function initBuffers() {
squareVerticesPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesPositionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
]), gl.STATIC_DRAW);
}
function drawScene() {
gl.clear(gl.COLOR_BUFFER_BIT);
for (let i = 0; i < 2; i++) {
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesPositionBuffer);
gl.vertexAttribPointer(vertexPositionAttribute[i], 3, gl.FLOAT, false, 0, 0);
gl.useProgram(program[i]);
gl.uniform1f(depthUniform[i], i + 1);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
window.requestAnimationFrame(drawScene);
}
</script>
</body>
</html>

View File

@ -27,6 +27,7 @@ const EXAMPLE_URL = "http://example.com/browser/browser/devtools/shadereditor/te
const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html";
const SHADER_ORDER_URL = EXAMPLE_URL + "doc_shader-order.html";
const MULTIPLE_CONTEXTS_URL = EXAMPLE_URL + "doc_multiple-contexts.html";
const OVERLAPPING_GEOMETRY_CANVAS_URL = EXAMPLE_URL + "doc_overlapping-geometry.html";
// All tests are asynchronous.
waitForExplicitFinish();

View File

@ -95,6 +95,10 @@ function Tooltip(doc) {
module.exports.Tooltip = Tooltip;
Tooltip.prototype = {
defaultPosition: "before_start",
defaultOffsetX: 0,
defaultOffsetY: 0,
/**
* Show the tooltip. It might be wise to append some content first if you
* don't want the tooltip to be empty. You may access the content of the
@ -105,9 +109,12 @@ Tooltip.prototype = {
* https://developer.mozilla.org/en-US/docs/XUL/PopupGuide/Positioning
* Defaults to before_start
*/
show: function(anchor, position="before_start") {
show: function(anchor,
position = this.defaultPosition,
x = this.defaultOffsetX,
y = this.defaultOffsetY) {
this.panel.hidden = false;
this.panel.openPopup(anchor, position);
this.panel.openPopup(anchor, position, x, y);
},
/**
@ -258,11 +265,44 @@ Tooltip.prototype = {
},
/**
* Fill the tooltip with an image, displayed over a tiled background useful
* for transparent images.
* Also adds the image dimension as a label at the bottom.
* Sets some text as the content of this tooltip.
*
* @param string[] messages
* A list of text messages.
*/
setImageContent: function(imageUrl, maxDim=400) {
setTextContent: function(...messages) {
let vbox = this.doc.createElement("vbox");
vbox.className = "devtools-tooltip-simple-text-container";
vbox.setAttribute("flex", "1");
for (let text of messages) {
let description = this.doc.createElement("description");
description.setAttribute("flex", "1");
description.className = "devtools-tooltip-simple-text";
description.textContent = text;
vbox.appendChild(description);
}
this.content = vbox;
},
/**
* Fill the tooltip with an image, displayed over a tiled background useful
* for transparent images. Also adds the image dimension as a label at the
* bottom.
* @param {string} imageUrl
* The url to load the image from
* @param {Object} options
* The following options are supported:
* - resized : whether or not the image identified by imageUrl has been
* resized before this function was called.
* - naturalWidth/naturalHeight : the original size of the image before
* it was resized, if if was resized before this function was called.
* If not provided, will be measured on the loaded image.
* - maxDim : if the image should be resized before being shown, pass
* a number here
*/
setImageContent: function(imageUrl, options={}) {
// Main container
let vbox = this.doc.createElement("vbox");
vbox.setAttribute("align", "center")
@ -279,9 +319,9 @@ Tooltip.prototype = {
// Display the image
let image = this.doc.createElement("image");
image.setAttribute("src", imageUrl);
if (maxDim) {
image.style.maxWidth = maxDim + "px";
image.style.maxHeight = maxDim + "px";
if (options.maxDim) {
image.style.maxWidth = options.maxDim + "px";
image.style.maxHeight = options.maxDim + "px";
}
tiles.appendChild(image);
@ -294,11 +334,9 @@ Tooltip.prototype = {
imgObj.onload = null;
// Display dimensions
label.textContent = imgObj.naturalWidth + " x " + imgObj.naturalHeight;
if (imgObj.naturalWidth > maxDim ||
imgObj.naturalHeight > maxDim) {
label.textContent += " *";
}
let w = options.naturalWidth || imgObj.naturalWidth;
let h = options.naturalHeight || imgObj.naturalHeight;
label.textContent = w + " x " + h;
}
},
@ -309,7 +347,9 @@ Tooltip.prototype = {
setCssBackgroundImageContent: function(cssBackground, sheetHref, maxDim=400) {
let uri = getBackgroundImageUri(cssBackground, sheetHref);
if (uri) {
this.setImageContent(uri, maxDim);
this.setImageContent(uri, {
maxDim: maxDim
});
}
},

View File

@ -1,15 +1,25 @@
/* 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/. */
.errors,
.breakpoints {
width: 16px;
}
.breakpoint, .debugLocation, .breakpoint-debugLocation {
.error, .breakpoint, .debugLocation, .breakpoint-debugLocation {
display: inline-block;
margin-left: 5px;
width: 14px;
height: 14px;
width: 12px;
height: 12px;
background-repeat: no-repeat;
background-position: center center;
background-size: 12px;
background-position: center;
background-size: contain;
}
.error {
background-image: url("chrome://browser/skin/devtools/orion-error.png");
opacity: 0.75;
}
.breakpoint {
@ -23,4 +33,8 @@
.breakpoint.debugLocation {
background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
url("chrome://browser/skin/devtools/orion-breakpoint.png");
}
}
.error-line {
background: rgba(255,0,0,0.2);
}

View File

@ -6,38 +6,6 @@
const dbginfo = new WeakMap();
// Private functions
/**
* Adds a marker to the breakpoints gutter.
* Type should be either a 'breakpoint' or a 'debugLocation'.
*/
function addMarker(cm, line, type) {
let info = cm.lineInfo(line);
if (info.gutterMarkers)
return void info.gutterMarkers.breakpoints.classList.add(type);
let mark = cm.getWrapperElement().ownerDocument.createElement("div");
mark.className = type;
mark.innerHTML = "";
cm.setGutterMarker(info.line, "breakpoints", mark);
}
/**
* Removes a marker from the breakpoints gutter.
* Type should be either a 'breakpoint' or a 'debugLocation'.
*/
function removeMarker(cm, line, type) {
let info = cm.lineInfo(line);
if (!info || !info.gutterMarkers)
return;
info.gutterMarkers.breakpoints.classList.remove(type);
}
// These functions implement search within the debugger. Since
// search in the debugger is different from other components,
// we can't use search.js CodeMirror addon. This is a slightly
@ -155,7 +123,7 @@ function addBreakpoint(ctx, line, cond) {
let meta = dbginfo.get(ed);
let info = cm.lineInfo(line);
addMarker(cm, line, "breakpoint");
ed.addMarker(line, "breakpoints", "breakpoint");
meta.breakpoints[line] = { condition: cond };
info.handle.on("delete", function onDelete() {
@ -179,7 +147,7 @@ function removeBreakpoint(ctx, line) {
let info = cm.lineInfo(line);
meta.breakpoints[info.line] = null;
removeMarker(cm, info.line, "breakpoint");
ed.removeMarker(info.line, "breakpoints", "breakpoint");
ed.emit("breakpointRemoved", line);
}
@ -203,11 +171,11 @@ function getBreakpoints(ctx) {
* display the line on which the Debugger is currently paused.
*/
function setDebugLocation(ctx, line) {
let { ed, cm } = ctx;
let { ed } = ctx;
let meta = dbginfo.get(ed);
meta.debugLocation = line;
addMarker(cm, line, "debugLocation");
ed.addMarker(line, "breakpoints", "debugLocation");
}
/**
@ -226,11 +194,11 @@ function getDebugLocation(ctx) {
* also removes a visual anchor from the breakpoints gutter.
*/
function clearDebugLocation(ctx) {
let { ed, cm } = ctx;
let { ed } = ctx;
let meta = dbginfo.get(ed);
if (meta.debugLocation != null) {
removeMarker(cm, meta.debugLocation, "debugLocation");
ed.removeMarker(meta.debugLocation, "breakpoints", "debugLocation");
meta.debugLocation = null;
}
}

View File

@ -340,7 +340,7 @@ Editor.prototype = {
* Replaces contents of a text area within the from/to {line, ch}
* range. If neither from nor to arguments are provided works
* exactly like setText. If only from object is provided, inserts
* text at that point.
* text at that point, *overwriting* as many characters as needed.
*/
replaceText: function (value, from, to) {
let cm = editors.get(this);
@ -356,6 +356,15 @@ Editor.prototype = {
cm.replaceRange(value, from, to);
},
/**
* Inserts text at the specified {line, ch} position, shifting existing
* contents as necessary.
*/
insertText: function (value, at) {
let cm = editors.get(this);
cm.replaceRange(value, at, at);
},
/**
* Deselects contents of the text area.
*/
@ -370,7 +379,7 @@ Editor.prototype = {
* Marks the contents as clean and returns the current
* version number.
*/
markClean: function () {
setClean: function () {
let cm = editors.get(this);
this.version = cm.changeGeneration();
return this.version;
@ -519,6 +528,120 @@ Editor.prototype = {
this.setFirstVisibleLine(topLine);
},
/**
* Returns whether a marker of a specified class exists in a line's gutter.
*/
hasMarker: function (line, gutterName, markerClass) {
let cm = editors.get(this);
let info = cm.lineInfo(line);
if (!info)
return false;
let gutterMarkers = info.gutterMarkers;
if (!gutterMarkers)
return false;
let marker = gutterMarkers[gutterName];
if (!marker)
return false;
return marker.classList.contains(markerClass);
},
/**
* Adds a marker with a specified class to a line's gutter. If another marker
* exists on that line, the new marker class is added to its class list.
*/
addMarker: function (line, gutterName, markerClass) {
let cm = editors.get(this);
let info = cm.lineInfo(line);
if (!info)
return;
let gutterMarkers = info.gutterMarkers;
if (gutterMarkers) {
let marker = gutterMarkers[gutterName];
if (marker) {
marker.classList.add(markerClass);
return;
}
}
let marker = cm.getWrapperElement().ownerDocument.createElement("div");
marker.className = markerClass;
cm.setGutterMarker(info.line, gutterName, marker);
},
/**
* The reverse of addMarker. Removes a marker of a specified class from a
* line's gutter.
*/
removeMarker: function (line, gutterName, markerClass) {
if (!this.hasMarker(line, gutterName, markerClass))
return;
let cm = editors.get(this);
cm.lineInfo(line).gutterMarkers[gutterName].classList.remove(markerClass);
},
/**
* Remove all gutter markers in the gutter with the given name.
*/
removeAllMarkers: function (gutterName) {
let cm = editors.get(this);
cm.clearGutter(gutterName);
},
/**
* Handles attaching a set of events listeners on a marker. They should
* be passed as an object literal with keys as event names and values as
* function listeners. The line number, marker node and optional data
* will be passed as arguments to the function listener.
*
* You don't need to worry about removing these event listeners.
* They're automatically orphaned when clearing markers.
*/
setMarkerListeners: function(line, gutterName, markerClass, events, data) {
if (!this.hasMarker(line, gutterName, markerClass))
return;
let cm = editors.get(this);
let marker = cm.lineInfo(line).gutterMarkers[gutterName];
for (let name in events) {
let listener = events[name].bind(this, line, marker, data);
marker.addEventListener(name, listener);
}
},
/**
* Returns whether a line is decorated using the specified class name.
*/
hasLineClass: function (line, className) {
let cm = editors.get(this);
let info = cm.lineInfo(line);
if (!info)
return false;
return info.wrapClass == className;
},
/**
* Set a CSS class name for the given line, including the text and gutter.
*/
addLineClass: function (line, className) {
let cm = editors.get(this);
cm.addLineClass(line, "wrap", className);
},
/**
* The reverse of addLineClass.
*/
removeLineClass: function (line, className) {
let cm = editors.get(this);
cm.removeLineClass(line, "wrap", className);
},
destroy: function () {
this.container = null;
this.config = null;

View File

@ -376,7 +376,7 @@ StyleSheetEditor.prototype = {
if (callback) {
callback(returnFile);
}
this.sourceEditor.markClean();
this.sourceEditor.setClean();
}.bind(this));
};

View File

@ -34,8 +34,8 @@ validator.invalidAppType=Unknown app type: '%S'.
validator.invalidHostedPriviledges=Hosted App can't be type '%S'.
validator.noCertifiedSupport='certified' apps are not fully supported on the App manager.
validator.nonAbsoluteLaunchPath=Launch path has to be an absolute path starting with '/': '%S'
validator.invalidLaunchPath=Unable to access to app starting document '%S'
# LOCALIZATION NOTE (validator.invalidLaunchPathBadHttpCode): %1$S is the URI of
validator.accessFailedLaunchPath=Unable to access the app starting document '%S'
# LOCALIZATION NOTE (validator.accessFailedLaunchPathBadHttpCode): %1$S is the URI of
# the launch document, %2$S is the http error code.
validator.invalidLaunchPathBadHttpCode=Unable to access to app starting document '%1$S', got HTTP code %2$S
validator.accessFailedLaunchPathBadHttpCode=Unable to access the app starting document '%1$S', got HTTP code %2$S

View File

@ -2,6 +2,8 @@
- 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/. -->
<!ENTITY popups.label "Pop-ups">
<!ENTITY blockPopups.label "Block pop-up windows">
<!ENTITY blockPopups.accesskey "B">
<!ENTITY popupExceptions.label "Exceptions…">

View File

@ -2,6 +2,8 @@
- 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/. -->
<!ENTITY general.label "General">
<!ENTITY warnAddonInstall.label "Warn me when sites try to install add-ons">
<!ENTITY warnAddonInstall.accesskey "W">

View File

@ -255,7 +255,11 @@ this.UITour = {
if (uri.schemeIs("chrome"))
return true;
if (!uri.schemeIs("https"))
let allowedSchemes = new Set(["https"]);
if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
allowedSchemes.add("http");
if (!allowedSchemes.has(uri.scheme))
return false;
this.importPermissions();

View File

@ -32,13 +32,12 @@ function is_element_hidden(element, msg) {
ok(is_hidden(element), msg);
}
function loadTestPage(callback, untrustedHost = false) {
function loadTestPage(callback, host = "https://example.com/") {
if (gTestTab)
gBrowser.removeTab(gTestTab);
let url = getRootDirectory(gTestPath) + "uitour.html";
if (untrustedHost)
url = url.replace("chrome://mochitests/content/", "http://example.com/");
url = url.replace("chrome://mochitests/content/", host);
gTestTab = gBrowser.addTab(url);
gBrowser.selectedTab = gTestTab;
@ -55,6 +54,8 @@ function loadTestPage(callback, untrustedHost = false) {
function test() {
Services.prefs.setBoolPref("browser.uitour.enabled", true);
let testUri = Services.io.newURI("http://example.com", null, null);
Services.perms.add(testUri, "uitour", Services.perms.ALLOW_ACTION);
waitForExplicitFinish();
@ -65,6 +66,7 @@ function test() {
gBrowser.removeTab(gTestTab);
delete window.gTestTab;
Services.prefs.clearUserPref("browser.uitour.enabled", true);
Services.perms.remove("example.com", "uitour");
});
function done() {
@ -98,6 +100,41 @@ function test() {
}
let tests = [
function test_untrusted_host(done) {
loadTestPage(function() {
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");
gContentAPI.showHighlight("urlbar");
is_element_hidden(highlight, "Highlight should not be shown on a untrusted host");
done();
}, "http://mochi.test:8888/");
},
function test_unsecure_host(done) {
loadTestPage(function() {
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");
gContentAPI.showHighlight("urlbar");
is_element_hidden(highlight, "Highlight should not be shown on a unsecure host");
done();
}, "http://example.com/");
},
function test_unsecure_host_override(done) {
Services.prefs.setBoolPref("browser.uitour.requireSecure", false);
loadTestPage(function() {
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");
gContentAPI.showHighlight("urlbar");
is_element_visible(highlight, "Highlight should be shown on a unsecure host when override pref is set");
Services.prefs.setBoolPref("browser.uitour.requireSecure", true);
done();
}, "http://example.com/");
},
function test_disabled(done) {
Services.prefs.setBoolPref("browser.uitour.enabled", false);
@ -110,17 +147,6 @@ let tests = [
Services.prefs.setBoolPref("browser.uitour.enabled", true);
done();
},
function test_untrusted_host(done) {
loadTestPage(function() {
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");
gContentAPI.showHighlight("urlbar");
is_element_hidden(highlight, "Highlight should not be shown on a untrusted domain");
done();
}, true);
},
function test_highlight(done) {
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -105,7 +105,7 @@ browser.jar:
skin/classic/browser/preferences/Options-sync.png (preferences/Options-sync.png)
#endif
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png)
@ -143,6 +143,7 @@ browser.jar:
skin/classic/browser/devtools/orion.css (devtools/orion.css)
skin/classic/browser/devtools/orion-container.css (devtools/orion-container.css)
skin/classic/browser/devtools/orion-task.png (devtools/orion-task.png)
skin/classic/browser/devtools/orion-error.png (devtools/orion-error.png)
skin/classic/browser/devtools/orion-breakpoint.png (devtools/orion-breakpoint.png)
skin/classic/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png)
skin/classic/browser/devtools/breadcrumbs-scrollbutton.png (devtools/breadcrumbs-scrollbutton.png)

View File

@ -21,12 +21,12 @@
-moz-margin-end: 3px;
}
richlistitem label {
#handlersView > richlistitem label {
-moz-margin-start: 1px;
margin-top: 2px;
}
richlistitem {
#handlersView > richlistitem {
min-height: 25px;
}

View File

@ -6,95 +6,115 @@
@namespace html "http://www.w3.org/1999/xhtml";
#preferences-home {
display: block;
}
#header {
margin-bottom: 18px;
}
.landingButton {
-moz-box-align: center;
-moz-box-orient: vertical;
}
.landingButton:hover {
cursor: pointer;
}
.landingButton-label {
margin-top: 4px;
}
.landingButton-icon {
display: block;
width: 32px;
height: 32px;
background-image: url("chrome://browser/skin/preferences/Options.png");
background-repeat: no-repeat;
}
.preference-icon {
display: block;
width: 32px;
height: 32px;
background-image: url("chrome://browser/skin/preferences/Options.png");
background-repeat: no-repeat;
margin: 0 20px;
}
.preference-icon[type="general"],
.landingButton-icon[type="general"] {
background-position: 0 0;
}
.preference-icon[type="content"],
.landingButton-icon[type="content"] {
background-position: -64px 0;
}
.preference-icon[type="applications"],
.landingButton-icon[type="applications"] {
background-position: -96px 0;
}
.preference-icon[type="privacy"],
.landingButton-icon[type="privacy"] {
background-position: -128px 0;
}
.preference-icon[type="security"],
.landingButton-icon[type="security"] {
background-position: -160px 0;
}
.preference-icon[type="advanced"],
.landingButton-icon[type="advanced"] {
background-position: -192px 0;
}
.preference-icon[type="sync"],
.landingButton-icon[type="sync"] {
background-image: url("chrome://browser/skin/preferences/Options-sync.png");
}
caption {
font-size: 20px;
font-size: 1.667rem;
}
.heading {
height: 50px;
background-color: rgba(192,199,210,0.7);
border-radius: 5px 5px 0 0;
margin-bottom: 15px;
-moz-box-align: center;
.main-content {
max-width: 800px;
}
prefpane > .content-box {
overflow: auto;
}
/* Category List */
#categories {
-moz-appearance: none;
border: none;
-moz-margin-end: -1px;
background-color: transparent;
position: relative;
margin-top: 41px;
}
.category {
-moz-appearance: none;
border-width: 1px;
-moz-border-end-width: 0;
border-style: solid;
border-color: transparent;
padding: 9px 4px 10px;
-moz-padding-end: 8px;
-moz-box-align: center;
overflow: hidden;
min-height: 0;
color: WindowText;
height: 52px;
}
.category:-moz-locale-dir(ltr) {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.category:-moz-locale-dir(rtl) {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.category[selected] {
background-color: -moz-Field;
color: -moz-FieldText;
border-color: ThreeDShadow;
}
.category-name {
font-size: 1.5rem;
-moz-padding-end: 24px;
}
/* Maximize the size of the viewport when the window is small */
@media (max-width: 800px) {
.category-name {
display: none;
}
}
.category-icon {
width: 32px;
height: 32px;
margin: 0 6px;
-moz-margin-start: 6px;
-moz-margin-end: 5px;
list-style-image: url("chrome://browser/skin/preferences/Options.png");
}
#category-general > .category-icon {
-moz-image-region: rect(0, 32px, 32px, 0);
}
#category-content > .category-icon {
-moz-image-region: rect(0, 96px, 32px, 64px)
}
#category-application > .category-icon {
-moz-image-region: rect(0, 128px, 32px, 96px)
}
#category-privacy > .category-icon {
-moz-image-region: rect(0, 160px, 32px, 128px)
}
#category-security > .category-icon {
-moz-image-region: rect(0, 192px, 32px, 160px)
}
#category-advanced > .category-icon {
-moz-image-region: rect(0, 224px, 32px, 192px)
}
%ifdef MOZ_SERVICES_SYNC
#category-sync > .category-icon {
list-style-image: url("chrome://browser/skin/preferences/Options-sync.png");
}
%endif
/* Applications Pane Styles */
#applications-content {

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -169,7 +169,7 @@ browser.jar:
#endif
skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png)
@ -232,6 +232,7 @@ browser.jar:
skin/classic/browser/devtools/orion.css (devtools/orion.css)
skin/classic/browser/devtools/orion-container.css (devtools/orion-container.css)
skin/classic/browser/devtools/orion-task.png (devtools/orion-task.png)
skin/classic/browser/devtools/orion-error.png (devtools/orion-error.png)
skin/classic/browser/devtools/orion-breakpoint.png (devtools/orion-breakpoint.png)
skin/classic/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png)
* skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css)

View File

@ -14,12 +14,12 @@
margin-bottom: -1px;
}
richlistitem label {
#handlersView > richlistitem label {
-moz-margin-start: 3px;
margin-top: 2px;
}
richlistitem {
#handlersView > richlistitem {
min-height: 22px;
}

View File

@ -2,102 +2,119 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
- You can obtain one at http://mozilla.org/MPL/2.0/. */
%include ../../shared.inc
@import url("chrome://global/skin/inContentUI.css");
@namespace html "http://www.w3.org/1999/xhtml";
#preferences-home {
display: block;
}
#header {
margin-bottom: 18px;
}
.landingButton {
-moz-box-align: center;
-moz-box-orient: vertical;
border: none;
background: none;
box-shadow: none;
}
.landingButton:hover {
cursor: pointer;
}
.landingButton-label {
margin-top: 4px;
}
.landingButton-icon {
display: block;
width: 32px;
height: 32px;
background-image: url("chrome://browser/skin/preferences/Options.png");
background-repeat: no-repeat;
}
.preference-icon {
display: block;
width: 32px;
height: 32px;
background-image: url("chrome://browser/skin/preferences/Options.png");
background-repeat: no-repeat;
margin: 0 20px;
}
.preference-icon[type="general"],
.landingButton-icon[type="general"] {
background-position: 0 0;
}
.preference-icon[type="content"],
.landingButton-icon[type="content"] {
background-position: -64px 0;
}
.preference-icon[type="applications"],
.landingButton-icon[type="applications"] {
background-position: -96px 0;
}
.preference-icon[type="privacy"],
.landingButton-icon[type="privacy"] {
background-position: -128px 0;
}
.preference-icon[type="security"],
.landingButton-icon[type="security"] {
background-position: -160px 0;
}
.preference-icon[type="advanced"],
.landingButton-icon[type="advanced"] {
background-position: -192px 0;
}
.preference-icon[type="sync"],
.landingButton-icon[type="sync"] {
background-image: url("chrome://browser/skin/preferences/Options-sync.png");
}
caption {
font-size: 20px;
font-size: 1.667rem;
}
.heading {
height: 50px;
background-color: rgba(192,199,210,0.7);
border-radius: 5px 5px 0 0;
margin-bottom: 15px;
-moz-box-align: center;
.main-content {
max-width: 800px;
}
prefpane > .content-box {
overflow: auto;
}
/* Category List */
#categories {
-moz-appearance: none;
border: none;
-moz-margin-end: -1px;
background-color: transparent;
position: relative;
margin-top: 31px;
}
.category {
-moz-appearance: none;
color: #252F3B;
border-width: 1px;
border-style: solid;
border-color: transparent;
padding: 10px 4px;
-moz-padding-end: 8px;
-moz-box-align: center;
overflow: hidden;
min-height: 0;
height: 52px;
}
.category:-moz-locale-dir(ltr) {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.category:-moz-locale-dir(rtl) {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.category[selected] {
background-color: rgba(255, 255, 255, 0.35);
color: -moz-dialogtext;
border-color: rgba(50, 65, 92, 0.4);
-moz-border-end-color: #C9CFD7;
}
.category-name {
font-size: 1.5rem;
-moz-padding-end: 24px;
}
/* Maximize the size of the viewport when the window is small */
@media (max-width: 800px) {
.category-name {
display: none;
}
}
.category-icon {
width: 32px;
height: 32px;
-moz-margin-start: 6px;
list-style-image: url("chrome://browser/skin/preferences/Options.png");
}
#category-general > .category-icon {
-moz-image-region: rect(0, 32px, 32px, 0);
}
#category-content > .category-icon {
-moz-image-region: rect(0, 96px, 32px, 64px)
}
#category-application > .category-icon {
-moz-image-region: rect(0, 128px, 32px, 96px)
}
#category-privacy > .category-icon {
-moz-image-region: rect(0, 160px, 32px, 128px)
}
#category-security > .category-icon {
-moz-image-region: rect(0, 192px, 32px, 160px)
}
#category-advanced > .category-icon {
-moz-image-region: rect(0, 224px, 32px, 192px)
}
%ifdef MOZ_SERVICES_SYNC
#category-sync > .category-icon {
list-style-image: url("chrome://browser/skin/preferences/Options-sync.png");
}
%endif
/* Applications Pane Styles */
#applications-content {

View File

@ -121,11 +121,29 @@
background: #eee;
border-radius: 3px;
}
.devtools-tooltip.devtools-tooltip-panel .panel-arrowcontent {
/* If the tooltip uses a <panel> XUL element instead */
padding: 4px;
}
.devtools-tooltip-simple-text {
background: linear-gradient(1deg, transparent 0%, rgba(94,136,176,0.1) 100%);
max-width: 400px;
margin: 0 -4px; /* Compensate for the .panel-arrowcontent padding. */
padding: 8px 12px;
text-shadow: 0 1px 0 #fff;
white-space: pre-wrap;
}
.devtools-tooltip-simple-text:first-child {
margin-top: -4px;
}
.devtools-tooltip-simple-text:last-child {
margin-bottom: -4px;
}
.devtools-tooltip-tiles {
background-color: #eee;
background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -125,7 +125,7 @@ browser.jar:
#endif
skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png)
@ -167,6 +167,7 @@ browser.jar:
skin/classic/browser/devtools/orion.css (devtools/orion.css)
skin/classic/browser/devtools/orion-container.css (devtools/orion-container.css)
skin/classic/browser/devtools/orion-task.png (devtools/orion-task.png)
skin/classic/browser/devtools/orion-error.png (devtools/orion-error.png)
skin/classic/browser/devtools/orion-breakpoint.png (devtools/orion-breakpoint.png)
skin/classic/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png)
* skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css)
@ -402,7 +403,7 @@ browser.jar:
#endif
skin/classic/aero/browser/preferences/saveFile.png (preferences/saveFile-aero.png)
* skin/classic/aero/browser/preferences/preferences.css (preferences/preferences.css)
skin/classic/aero/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
* skin/classic/aero/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/aero/browser/preferences/applications.css (preferences/applications.css)
skin/classic/aero/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/aero/browser/social/services-16.png (social/services-16.png)
@ -444,6 +445,7 @@ browser.jar:
skin/classic/aero/browser/devtools/orion.css (devtools/orion.css)
skin/classic/aero/browser/devtools/orion-container.css (devtools/orion-container.css)
skin/classic/aero/browser/devtools/orion-task.png (devtools/orion-task.png)
skin/classic/aero/browser/devtools/orion-error.png (devtools/orion-error.png)
skin/classic/aero/browser/devtools/orion-breakpoint.png (devtools/orion-breakpoint.png)
skin/classic/aero/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png)
* skin/classic/aero/browser/devtools/webconsole.css (devtools/webconsole.css)

View File

@ -21,12 +21,12 @@
-moz-margin-end: 3px;
}
richlistitem label {
#handlersView > richlistitem label {
-moz-margin-start: 1px;
margin-top: 2px;
}
richlistitem {
#handlersView > richlistitem {
min-height: 22px;
}

View File

@ -6,98 +6,116 @@
@namespace html "http://www.w3.org/1999/xhtml";
#preferences-home {
display: block;
}
#header {
margin-bottom: 18px;
}
.landingButton {
-moz-box-align: center;
-moz-box-orient: vertical;
border: none;
background: none;
box-shadow: none;
}
.landingButton:hover {
cursor: pointer;
}
.landingButton-label {
margin-top: 4px;
}
.landingButton-icon {
display: block;
width: 32px;
height: 32px;
background-image: url("chrome://browser/skin/preferences/Options.png");
background-repeat: no-repeat;
}
.preference-icon {
display: block;
width: 32px;
height: 32px;
background-image: url("chrome://browser/skin/preferences/Options.png");
background-repeat: no-repeat;
margin: 0 20px;
}
.preference-icon[type="general"],
.landingButton-icon[type="general"] {
background-position: 0 0;
}
.preference-icon[type="content"],
.landingButton-icon[type="content"] {
background-position: -64px 0;
}
.preference-icon[type="applications"],
.landingButton-icon[type="applications"] {
background-position: -96px 0;
}
.preference-icon[type="privacy"],
.landingButton-icon[type="privacy"] {
background-position: -128px 0;
}
.preference-icon[type="security"],
.landingButton-icon[type="security"] {
background-position: -160px 0;
}
.preference-icon[type="advanced"],
.landingButton-icon[type="advanced"] {
background-position: -192px 0;
}
.preference-icon[type="sync"],
.landingButton-icon[type="sync"] {
background-image: url("chrome://browser/skin/preferences/Options-sync.png");
}
caption {
font-size: 20px;
font-size: 1.667rem;
}
.heading {
height: 50px;
background-color: rgba(192,199,210,0.7);
border-radius: 5px 5px 0 0;
margin-bottom: 15px;
-moz-box-align: center;
.main-content {
max-width: 800px;
}
prefpane > .content-box {
overflow: auto;
}
/* Category List */
#categories {
-moz-appearance: none;
border: none;
-moz-margin-end: -1px;
background-color: transparent;
position: relative;
margin-top: 31px;
}
.category {
-moz-appearance: none;
background-color: transparent;
color: #252F3B;
padding: 10px 4px;
border-width: 1px;
border-style: solid;
border-color: transparent;
-moz-padding-end: 8px;
-moz-box-align: center;
overflow: hidden;
min-height: 0;
height: 52px;
}
.category:-moz-locale-dir(ltr) {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.category:-moz-locale-dir(rtl) {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.category[selected] {
background-color: rgba(255, 255, 255, 0.4);
color: #252F3B;
border-color: #C3CEDF;
-moz-border-end-color: #E2E9F2;
}
.category-name {
font-size: 1.5rem;
-moz-padding-end: 24px;
}
/* Maximize the size of the viewport when the window is small */
@media (max-width: 800px) {
.category-name {
display: none;
}
}
.category-icon {
width: 32px;
height: 32px;
margin: 0 6px;
-moz-margin-start: 6px;
-moz-margin-end: 5px;
list-style-image: url("chrome://browser/skin/preferences/Options.png");
}
#category-general > .category-icon {
-moz-image-region: rect(0, 32px, 32px, 0);
}
#category-content > .category-icon {
-moz-image-region: rect(0, 96px, 32px, 64px)
}
#category-application > .category-icon {
-moz-image-region: rect(0, 128px, 32px, 96px)
}
#category-privacy > .category-icon {
-moz-image-region: rect(0, 160px, 32px, 128px)
}
#category-security > .category-icon {
-moz-image-region: rect(0, 192px, 32px, 160px)
}
#category-advanced > .category-icon {
-moz-image-region: rect(0, 224px, 32px, 192px)
}
%ifdef MOZ_SERVICES_SYNC
#category-sync > .category-icon {
list-style-image: url("chrome://browser/skin/preferences/Options-sync.png");
}
%endif
/* Applications Pane Styles */
#applications-content {

View File

@ -37,7 +37,6 @@ import org.json.JSONObject;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
@ -1475,15 +1474,6 @@ abstract public class BrowserApp extends GeckoApp
return;
}
// If the keywordUrl is in ReadingList, convert the url to an about:reader url and load it.
final ContentResolver cr = getContentResolver();
final boolean inReadingList = BrowserDB.isReadingListItem(cr, keywordUrl);
if (inReadingList) {
final String readerUrl = ReaderModeUtils.getAboutReaderForUrl(keywordUrl);
Tabs.getInstance().loadUrl(readerUrl, Tabs.LOADURL_USER_ENTERED);
return;
}
recordSearch(null, "barkeyword");
// Otherwise, construct a search query from the bookmark keyword.

View File

@ -32,18 +32,29 @@ public final class VideoPlayer extends Activity {
MediaController mediaController = new MediaController(this);
mediaController.setAnchorView(mVideoView);
Intent intent = getIntent();
Uri data = intent.getData();
if (data == null)
final Uri data = intent.getData();
if (data == null) {
return;
}
String spec = null;
if ("vnd.youtube".equals(data.getScheme())) {
String ssp = data.getSchemeSpecificPart();
String id = ssp.substring(0, ssp.indexOf('?'));
int paramIndex = ssp.indexOf('?');
String id;
if (paramIndex == -1) {
id = ssp;
} else {
id = ssp.substring(0, paramIndex);
}
spec = getSpecFromYouTubeVideoID(id);
}
if (spec == null)
if (spec == null) {
return;
Uri video = Uri.parse(spec);
}
final Uri video = Uri.parse(spec);
mVideoView.setMediaController(mediaController);
mVideoView.setVideoURI(video);
mVideoView.start();

View File

@ -6173,7 +6173,7 @@ k12.pa.us
k12.pr.us
k12.ri.us
k12.sc.us
k12.sd.us
// k12.sd.us Bug 934131 - South Dakota has a centralized hosting
k12.tn.us
k12.tx.us
k12.ut.us

View File

@ -185,9 +185,6 @@ Tester.prototype = {
var failCount = this.tests.reduce(function(a, f) a + f.failCount, 0);
var todoCount = this.tests.reduce(function(a, f) a + f.todoCount, 0);
if (failCount > 0 && this.runUntilFailure)
this.repeat = 0;
if (this.repeat > 0) {
--this.repeat;
this.currentTestIndex = -1;
@ -222,6 +219,12 @@ Tester.prototype = {
}
},
haltTests: function Tester_haltTests() {
// Do not run any further tests
this.currentTestIndex = this.tests.length - 1;
this.repeat = 0;
},
observe: function Tester_observe(aSubject, aTopic, aData) {
if (!aTopic) {
this.onConsoleMessage(aSubject);
@ -354,6 +357,10 @@ Tester.prototype = {
this.dumper.dump("INFO TEST-END | " + this.currentTest.path + " | finished in " + time + "ms\n");
this.currentTest.setDuration(time);
if (this.runUntilFailure && this.currentTest.failCount > 0) {
this.haltTests();
}
testScope.destroy();
this.currentTest.scope = null;
}

View File

@ -392,9 +392,9 @@ def MochitestCommand(func):
func = repeat(func)
runUntilFailure = CommandArgument("--run-until-failure", action='store_true',
help='Run a test repeatedly and stops on the first time the test fails. ' \
'Only available when running a single test. Default cap is 30 runs, ' \
'which can be overwritten with the --repeat parameter.')
help='Run tests repeatedly and stops on the first time a test fails. ' \
'Default cap is 30 runs, which can be overwritten ' \
'with the --repeat parameter.')
func = runUntilFailure(func)
slow = CommandArgument('--slow', action='store_true',

View File

@ -274,9 +274,8 @@ class MochitestOptions(optparse.OptionParser):
[["--run-until-failure"],
{ "action": "store_true",
"dest": "runUntilFailure",
"help": "Run a test repeatedly and stops on the first time the test fails. "
"Only available when running a single test. Default cap is 30 runs, "
"which can be overwritten with the --repeat parameter.",
"help": "Run tests repeatedly and stops on the first time a test fails. "
"Default cap is 30 runs, which can be overwritten with the --repeat parameter.",
"default": False,
}],
[["--run-only-tests"],
@ -495,8 +494,6 @@ class MochitestOptions(optparse.OptionParser):
mochitest.immersiveHelperPath)
if options.runUntilFailure:
if not os.path.isfile(os.path.join(mochitest.oldcwd, os.path.dirname(__file__), mochitest.getTestRoot(options), options.testPath)):
self.error("--run-until-failure can only be used together with --test-path specifying a single test.")
if not options.repeat:
options.repeat = 29

View File

@ -214,6 +214,10 @@ TestRunner.error = function(msg) {
dump(msg + "\n");
}
if (TestRunner.runUntilFailure) {
TestRunner._haltTests = true;
}
if (TestRunner.debugOnFailure) {
// You've hit this line because you requested to break into the
// debugger upon a testcase failure on your test run.
@ -366,10 +370,7 @@ TestRunner.runNextTest = function() {
TestRunner.onComplete();
}
var failCount = parseInt($("fail-count").innerHTML);
var stopLooping = failCount > 0 && TestRunner.runUntilFailure;
if (TestRunner._currentLoop <= TestRunner.repeat && !stopLooping) {
if (TestRunner._currentLoop <= TestRunner.repeat && !TestRunner._haltTests) {
TestRunner._currentLoop++;
TestRunner.resetTests(TestRunner._urls);
TestRunner._loopIsRestarting = true;

View File

@ -1,10 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
ifdef MOZ_CRASHREPORTER
MOCHITEST_BROWSER_FILES += \
browser_thumbnails_background_crash.js \
thumbnails_crash_content_helper.js \
$(NULL)
endif

View File

@ -6,11 +6,13 @@ support-files =
head.js
privacy_cache_control.sjs
thumbnails_background.sjs
thumbnails_crash_content_helper.js
thumbnails_update.sjs
[browser_thumbnails_background.js]
# Too many intermittent failures (bug 931889)
skip-if = os == "linux"
[browser_thumbnails_background_crash.js]
[browser_thumbnails_bug726727.js]
[browser_thumbnails_bug727765.js]
[browser_thumbnails_bug818225.js]

View File

@ -37,6 +37,9 @@
// server updates.
const kKeyFilename = "urlclassifierkey3.txt";
Components.utils.import("resource://gre/modules/osfile.jsm");
Components.utils.import("resource://gre/modules/Promise.jsm");
/**
* A key manager for UrlCrypto. There should be exactly one of these
* per appplication, and all UrlCrypto's should share it. This is
@ -234,13 +237,15 @@ PROT_UrlCryptoKeyManager.prototype.unUrlSafe = function(key)
/**
* Set a new key and serialize it to disk.
*
* @param clientKey String containing the base64-encoded client key
* @param clientKey String containing the base64-encoded client key
* we wish to use
*
* @param wrappedKey String containing the opaque base64-encoded WrappedKey
* the server gave us (i.e., K_C encrypted with K_S)
*
* @returns Promise of a boolean indicating whether we succeeded in replacing
*/
PROT_UrlCryptoKeyManager.prototype.replaceKey_ = function(clientKey,
PROT_UrlCryptoKeyManager.prototype.replaceKey_ = function(clientKey,
wrappedKey) {
if (this.clientKey_)
G_Debug(this, "Replacing " + this.clientKey_ + " with " + clientKey);
@ -250,57 +255,52 @@ PROT_UrlCryptoKeyManager.prototype.replaceKey_ = function(clientKey,
function(c) { return c.charCodeAt(0); });
this.wrappedKey_ = wrappedKey;
this.serializeKey_(this.clientKey_, this.wrappedKey_);
let promise = this.serializeKey_(this.clientKey_, this.wrappedKey_);
if (this.onNewKey_) {
this.onNewKey_();
}
return promise.then(() => {
if (this.onNewKey_) {
this.onNewKey_();
}
return true;
});
}
/**
* Try to write the key to disk so we can fall back on it. Fail
* silently if we cannot. The keys are serialized in protocol4 format.
*
* @returns Boolean indicating whether we succeeded in serializing
* @returns Promise of a boolean indicating whether we succeeded in serializing
*/
PROT_UrlCryptoKeyManager.prototype.serializeKey_ = function() {
var map = {};
map[this.CLIENT_KEY_NAME] = this.clientKey_;
map[this.WRAPPED_KEY_NAME] = this.wrappedKey_;
try {
var keyfile = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties)
.get("ProfD", Ci.nsILocalFile); /* profile directory */
keyfile.append(this.keyFilename_);
let keypath = OS.Path.join(OS.Constants.Path.profileDir, this.keyFilename_);
if (!this.clientKey_ || !this.wrappedKey_) {
keyfile.remove(true);
return;
}
// if we have an invalid client key or wrapped key, we remove the
// invalid keyfile from disk
if (!this.clientKey_ || !this.wrappedKey_) {
return OS.File.remove(keypath).then(() => false,
e => {
if (!e.becauseNoSuchFile)
throw e;
return false;
});
}
var data = (new G_Protocol4Parser()).serialize(map);
try {
var stream = Cc["@mozilla.org/network/file-output-stream;1"]
.createInstance(Ci.nsIFileOutputStream);
stream.init(keyfile,
0x02 | 0x08 | 0x20 /* PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE */,
-1 /* default perms */, 0 /* no special behavior */);
stream.write(data, data.length);
} finally {
stream.close();
}
return true;
} catch(e) {
let data = (new G_Protocol4Parser()).serialize(map);
let encoder = new TextEncoder();
let array = encoder.encode(data);
let promise = OS.File.writeAtomic(keypath, array, { tmpPath: keypath + ".tmp",
flush: false });
return promise.then(() => true,
e => {
G_Error(this, "Failed to serialize new key: " + e);
return false;
}
});
}
/**
@ -308,7 +308,10 @@ PROT_UrlCryptoKeyManager.prototype.serializeKey_ = function() {
* request. Try to parse it and set this key as the new one if we can.
*
* @param responseText String containing the protocol4 getkey response
*/
*
* @returns Promise of a boolean indicating whether we succeeded in setting
* the new key
*/
PROT_UrlCryptoKeyManager.prototype.onGetKeyResponse = function(responseText) {
var response = (new G_Protocol4Parser).parse(responseText);
@ -320,9 +323,10 @@ PROT_UrlCryptoKeyManager.prototype.onGetKeyResponse = function(responseText) {
if (response && clientKey && wrappedKey) {
G_Debug(this, "Got new key from: " + responseText);
this.replaceKey_(clientKey, wrappedKey);
return this.replaceKey_(clientKey, wrappedKey);
} else {
G_Debug(this, "Not a valid response for /newkey");
return Promise.resolve(false);
}
}
@ -344,7 +348,10 @@ PROT_UrlCryptoKeyManager.prototype.onNewKey = function(callback)
*
* This method should be invoked early, like when the user's profile
* becomes available.
*/
*
* @returns Promise of a boolean indicating whether we succeeded in
* loading old key
*/
PROT_UrlCryptoKeyManager.prototype.maybeLoadOldKey = function() {
var oldKey = null;
@ -369,12 +376,12 @@ PROT_UrlCryptoKeyManager.prototype.maybeLoadOldKey = function() {
}
} catch(e) {
G_Debug(this, "Caught " + e + " trying to read keyfile");
return;
return Promise.resolve(false);
}
if (!oldKey) {
G_Debug(this, "Couldn't find old key.");
return;
return Promise.resolve(false);
}
oldKey = (new G_Protocol4Parser).parse(oldKey);
@ -383,8 +390,9 @@ PROT_UrlCryptoKeyManager.prototype.maybeLoadOldKey = function() {
if (oldKey && clientKey && wrappedKey && !this.hasKey()) {
G_Debug(this, "Read old key from disk.");
this.replaceKey_(clientKey, wrappedKey);
return this.replaceKey_(clientKey, wrappedKey);
}
return Promise.resolve(false);
}
PROT_UrlCryptoKeyManager.prototype.shutdown = function() {
@ -393,100 +401,3 @@ PROT_UrlCryptoKeyManager.prototype.shutdown = function() {
this.fetcher_ = null;
}
}
#ifdef DEBUG
/**
* Cheesey tests
*/
function TEST_PROT_UrlCryptoKeyManager() {
if (G_GDEBUG) {
var z = "urlcryptokeymanager UNITTEST";
G_debugService.enableZone(z);
G_Debug(z, "Starting");
// Let's not clobber any real keyfile out there
var kf = "keytest.txt";
// Let's be able to clean up after ourselves
function removeTestFile(f) {
var file = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties)
.get("ProfD", Ci.nsILocalFile); /* profile directory */
file.append(f);
if (file.exists())
file.remove(false /* do not recurse */);
};
removeTestFile(kf);
var km = new PROT_UrlCryptoKeyManager(kf, true /* testing */);
// CASE: simulate nothing on disk, then get something from server
G_Assert(z, !km.hasKey(), "KM already has key?");
km.maybeLoadOldKey();
G_Assert(z, !km.hasKey(), "KM loaded nonexistent key?");
km.onGetKeyResponse(null);
G_Assert(z, !km.hasKey(), "KM got key from null response?");
km.onGetKeyResponse("");
G_Assert(z, !km.hasKey(), "KM got key from empty response?");
km.onGetKeyResponse("aslkaslkdf:34:a230\nskdjfaljsie");
G_Assert(z, !km.hasKey(), "KM got key from garbage response?");
var realResponse = "clientkey:24:zGbaDbx1pxoYe7siZYi8VA==\n" +
"wrappedkey:24:MTr1oDt6TSOFQDTvKCWz9PEn";
km.onGetKeyResponse(realResponse);
// Will have written it to file as a side effect
G_Assert(z, km.hasKey(), "KM couldn't get key from real response?");
G_Assert(z, km.clientKey_ == "zGbaDbx1pxoYe7siZYi8VA==",
"Parsed wrong client key from response?");
G_Assert(z, km.wrappedKey_ == "MTr1oDt6TSOFQDTvKCWz9PEn",
"Parsed wrong wrapped key from response?");
// CASE: simulate something on disk, then get something from server
km = new PROT_UrlCryptoKeyManager(kf, true /* testing */);
G_Assert(z, !km.hasKey(), "KM already has key?");
km.maybeLoadOldKey();
G_Assert(z, km.hasKey(), "KM couldn't load existing key from disk?");
G_Assert(z, km.clientKey_ == "zGbaDbx1pxoYe7siZYi8VA==",
"Parsed wrong client key from disk?");
G_Assert(z, km.wrappedKey_ == "MTr1oDt6TSOFQDTvKCWz9PEn",
"Parsed wrong wrapped key from disk?");
var realResponse2 = "clientkey:24:dtmbEN1kgN/LmuEoYifaFw==\n" +
"wrappedkey:24:MTpPH3pnLDKihecOci+0W5dk";
km.onGetKeyResponse(realResponse2);
// Will have written it to disk
G_Assert(z, km.hasKey(), "KM couldn't replace key from server response?");
G_Assert(z, km.clientKey_ == "dtmbEN1kgN/LmuEoYifaFw==",
"Replace client key from server failed?");
G_Assert(z, km.wrappedKey == "MTpPH3pnLDKihecOci+0W5dk",
"Replace wrapped key from server failed?");
// CASE: check overwriting a key on disk
km = new PROT_UrlCryptoKeyManager(kf, true /* testing */);
G_Assert(z, !km.hasKey(), "KM already has key?");
km.maybeLoadOldKey();
G_Assert(z, km.hasKey(), "KM couldn't load existing key from disk?");
G_Assert(z, km.clientKey_ == "dtmbEN1kgN/LmuEoYifaFw==",
"Replace client on from disk failed?");
G_Assert(z, km.wrappedKey_ == "MTpPH3pnLDKihecOci+0W5dk",
"Replace wrapped key on disk failed?");
// Test that we only fetch at most two getkey's per lifetime of the manager
km = new PROT_UrlCryptoKeyManager(kf, true /* testing */);
km.reKey();
for (var i = 0; i < km.MAX_REKEY_TRIES; i++)
G_Assert(z, km.maybeReKey(), "Couldn't rekey?");
G_Assert(z, !km.maybeReKey(), "Rekeyed when max hit");
removeTestFile(kf);
G_Debug(z, "PASSED");
}
}
#endif

View File

@ -0,0 +1,67 @@
/* 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/. */
Cu.import("resource://gre/modules/osfile.jsm");
var kf = "keytest.txt"; // not an actual keyfile
function run_test() {
run_next_test();
}
add_task(function empty_disk() {
var jslib = Cc["@mozilla.org/url-classifier/jslib;1"]
.getService().wrappedJSObject;
this.PROT_UrlCryptoKeyManager = jslib.PROT_UrlCryptoKeyManager;
yield OS.File.remove(kf);
do_print("simulate nothing on disk, then get something from server");
var km = new PROT_UrlCryptoKeyManager(kf, true);
do_check_false(km.hasKey()); // KM already has key?
km.maybeLoadOldKey();
do_check_false(km.hasKey()); // KM loaded nonexistent key?
yield km.onGetKeyResponse(null);
do_check_false(km.hasKey()); // KM got key from null response?
yield km.onGetKeyResponse("");
do_check_false(km.hasKey()); // KM got key from an empty response?
yield km.onGetKeyResponse("aslkaslkdf:34:a230\nskdjfaljsie");
do_check_false(km.hasKey()); // KM got key from garbage response?
var realResponse = "clientkey:24:zGbaDbx1pxoYe7siZYi8VA==\n" +
"wrappedkey:24:MTr1oDt6TSOFQDTvKCWz9PEn";
yield km.onGetKeyResponse(realResponse);
// Will have written it to the file as a side effect
do_check_true(km.hasKey()); // KM could not get key from a real response?
do_check_eq(km.clientKey_, "zGbaDbx1pxoYe7siZYi8VA=="); // Parsed wrong client key from response?
do_check_eq(km.wrappedKey_, "MTr1oDt6TSOFQDTvKCWz9PEn"); // Parsed wrong wrapped key from response?
do_print("simulate something on disk, then get something from server");
var km = new PROT_UrlCryptoKeyManager(kf, true);
do_check_false(km.hasKey()); // KM already has key?
yield km.maybeLoadOldKey();
do_check_true(km.hasKey()); // KM couldn't load existing key from disk?
do_check_eq(km.clientKey_ , "zGbaDbx1pxoYe7siZYi8VA=="); // Parsed wrong client key from disk?
do_check_eq(km.wrappedKey_, "MTr1oDt6TSOFQDTvKCWz9PEn"); // Parsed wrong wrapped key from disk?
var realResponse2 = "clientkey:24:dtmbEN1kgN/LmuEoYifaFw==\n" +
"wrappedkey:24:MTpPH3pnLDKihecOci+0W5dk";
yield km.onGetKeyResponse(realResponse2);
do_check_true(km.hasKey()); // KM couldn't replace key from server response?
do_check_eq(km.clientKey_, "dtmbEN1kgN/LmuEoYifaFw=="); // Replace client key from server failed?
do_check_eq(km.wrappedKey_, "MTpPH3pnLDKihecOci+0W5dk"); // Replace wrapped key from server failed?
do_print("check overwriting a key on disk");
km = new PROT_UrlCryptoKeyManager(kf, true);
do_check_false(km.hasKey()); // KM already has key?
yield km.maybeLoadOldKey();
do_check_true(km.hasKey()); // KM couldn't load existing key from file?
do_check_eq(km.clientKey_, "dtmbEN1kgN/LmuEoYifaFw=="); // Replace client key on disk failed?
do_check_eq(km.wrappedKey_, "MTpPH3pnLDKihecOci+0W5dk"); // Replace wrapped key on disk failed?
do_print("Test that we only fetch at most two getkey's per lifetime of the manager");
var km = new PROT_UrlCryptoKeyManager(kf, true);
km.reKey();
for (var i = 0; i < km.MAX_REKEY_TRIES; i++)
do_check_true(km.maybeReKey()); // Couldn't rekey?
do_check_false(km.maybeReKey()); // Rekeyed when max hit?
yield OS.File.remove(kf);
});

View File

@ -5,6 +5,7 @@ support-files =
data/digest1.chunk
data/digest2.chunk
[test_keymanager.js]
[test_addsub.js]
[test_backoff.js]
[test_dbservice.js]

View File

@ -110,6 +110,13 @@ function delayedResolve(value) {
return deferred.promise;
}
types.addDictType("imageData", {
// The image data
data: "nullable:longstring",
// The original image dimensions
size: "json"
});
/**
* We only send nodeValue up to a certain size by default. This stuff
* controls that size.
@ -255,8 +262,12 @@ var NodeActor = protocol.ActorClass({
* A null return value means the node isn't an image
* An empty string return value means the node is an image but image data
* could not be retrieved (missing/broken image).
*
* Accepts a maxDim request parameter to resize images that are larger. This
* is important as the resizing occurs server-side so that image-data being
* transfered in the longstring back to the client will be that much smaller
*/
getImageData: method(function() {
getImageData: method(function(maxDim) {
let isImg = this.rawNode.tagName.toLowerCase() === "img";
let isCanvas = this.rawNode.tagName.toLowerCase() === "canvas";
@ -264,29 +275,44 @@ var NodeActor = protocol.ActorClass({
return null;
}
let imageData;
if (isImg) {
let canvas = this.rawNode.ownerDocument.createElement("canvas");
canvas.width = this.rawNode.naturalWidth;
canvas.height = this.rawNode.naturalHeight;
let ctx = canvas.getContext("2d");
try {
// This will fail if the image is missing
ctx.drawImage(this.rawNode, 0, 0);
imageData = canvas.toDataURL("image/png");
} catch (e) {
imageData = "";
}
} else if (isCanvas) {
imageData = this.rawNode.toDataURL("image/png");
// Get the image resize ratio if a maxDim was provided
let resizeRatio = 1;
let imgWidth = isImg ? this.rawNode.naturalWidth : this.rawNode.width;
let imgHeight = isImg ? this.rawNode.naturalHeight : this.rawNode.height;
let imgMax = Math.max(imgWidth, imgHeight);
if (maxDim && imgMax > maxDim) {
resizeRatio = maxDim / imgMax;
}
return LongStringActor(this.conn, imageData);
}, {
request: {},
response: {
data: RetVal("nullable:longstring")
// Create a canvas to copy the rawNode into and get the imageData from
let canvas = this.rawNode.ownerDocument.createElement("canvas");
canvas.width = imgWidth * resizeRatio;
canvas.height = imgHeight * resizeRatio;
let ctx = canvas.getContext("2d");
// Copy the rawNode image or canvas in the new canvas and extract data
let imageData;
// This may fail if the image is missing
try {
ctx.drawImage(this.rawNode, 0, 0, canvas.width, canvas.height);
imageData = canvas.toDataURL("image/png");
} catch (e) {
imageData = "";
}
return {
data: LongStringActor(this.conn, imageData),
size: {
naturalWidth: imgWidth,
naturalHeight: imgHeight,
width: canvas.width,
height: canvas.height,
resized: resizeRatio !== 1
}
}
}, {
request: {maxDim: Arg(0, "nullable:number")},
response: RetVal("imageData")
}),
/**

View File

@ -140,6 +140,24 @@ let ProgramActor = protocol.ActorClass({
oneway: true
}),
/**
* Prevents any geometry from being rendered using this program.
*/
blackbox: method(function() {
this.observer.cache.blackboxedPrograms.add(this.program);
}, {
oneway: true
}),
/**
* Allows geometry to be rendered using this program.
*/
unblackbox: method(function() {
this.observer.cache.blackboxedPrograms.delete(this.program);
}, {
oneway: true
}),
/**
* Returns a cached ShaderActor instance based on the required shader type.
*
@ -492,6 +510,15 @@ let WebGLInstrumenter = {
"uniform1fv", "uniform2fv", "uniform3fv", "uniform4fv",
"uniformMatrix2fv", "uniformMatrix3fv", "uniformMatrix4fv"
]
}, {
timing: "after",
functions: ["useProgram"]
}, {
timing: "before",
callback: "draw_",
functions: [
"drawArrays", "drawElements"
]
}]
// TODO: It'd be a good idea to handle other functions as well:
// - getActiveUniform
@ -577,7 +604,7 @@ WebGLObserver.prototype = {
*/
toggleVertexAttribArray: function(gl, glArgs) {
glArgs[0] = this.cache.call("getCurrentAttributeLocation", glArgs[0]);
return glArgs[0] < 0;
return glArgs[0] < 0; // Return true to break original function call.
},
/**
@ -590,7 +617,7 @@ WebGLObserver.prototype = {
*/
attribute_: function(gl, glArgs) {
glArgs[0] = this.cache.call("getCurrentAttributeLocation", glArgs[0]);
return glArgs[0] < 0;
return glArgs[0] < 0; // Return true to break original function call.
},
/**
@ -603,7 +630,37 @@ WebGLObserver.prototype = {
*/
uniform_: function(gl, glArgs) {
glArgs[0] = this.cache.call("getCurrentUniformLocation", glArgs[0]);
return !glArgs[0];
return !glArgs[0]; // Return true to break original function call.
},
/**
* Called immediately *after* 'useProgram' is requested in the context.
*
* @param WebGLRenderingContext gl
* The WebGL context initiating this call.
* @param array glArgs
* Overridable arguments with which the function is called.
* @param void glResult
* The returned value of the original function call.
*/
useProgram: function(gl, glArgs, glResult) {
// Manually keeping a cache and not using gl.getParameter(CURRENT_PROGRAM)
// because gl.get* functions are slow as potatoes.
this.cache.currentProgram = glArgs[0];
},
/**
* Called immediately *before* 'drawArrays' or 'drawElements' is requested
* in the context.
*
* @param WebGLRenderingContext gl
* The WebGL context initiating this call.
* @param array glArgs
* Overridable arguments with which the function is called.
*/
draw_: function(gl, glArgs) {
// Return true to break original function call.
return this.cache.blackboxedPrograms.has(this.cache.currentProgram);
},
/**
@ -634,6 +691,9 @@ WebGLObserver.prototype = {
function WebGLCache(observer) {
this._observer = observer;
this.currentProgram = null;
this.blackboxedPrograms = new Set();
this._shaders = new Map();
this._attributes = [];
this._uniforms = [];
@ -642,6 +702,16 @@ function WebGLCache(observer) {
}
WebGLCache.prototype = {
/**
* The current program in the observed WebGL context.
*/
currentProgram: null,
/**
* A set of blackboxed programs in the observed WebGL context.
*/
blackboxedPrograms: null,
/**
* Adds shader information to the cache.
*

View File

@ -5,6 +5,9 @@ support-files =
inspector-styles-data.html
inspector-traversal-data.html
nonchrome_unsafeDereference.html
inspector_getImageData.html
large-image.jpg
small-image.gif
[test_connection-manager.html]
[test_device.html]
@ -30,3 +33,4 @@ support-files =
[test_styles-svg.html]
[test_unsafeDereference.html]
[test_evalInGlobal-outerized_this.html]
[test_inspector_getImageData.html]

View File

@ -0,0 +1,19 @@
<html>
<head>
<body>
<img class="big-horizontal" src="large-image.jpg" style="width:500px;" />
<canvas class="big-vertical" style="width:500px;"></canvas>
<img class="small" src="small-image.gif"></img>
<script>
window.onload = () => {
var canvas = document.querySelector("canvas"), ctx = canvas.getContext("2d");
canvas.width = 1000;
canvas.height = 2000;
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 1000, 2000);
window.opener.postMessage('ready', '*')
}
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

View File

@ -0,0 +1,123 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=932937
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 932937</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
const promise = devtools.require("sdk/core/promise");
const inspector = devtools.require("devtools/server/actors/inspector");
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
}
var gWalker = null;
addTest(function setup() {
let url = document.getElementById("inspectorContent").href;
attachURL(url, function(err, client, tab, doc) {
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
let inspector = InspectorFront(client, tab);
promiseDone(inspector.getWalker().then(walker => {
gWalker = walker;
}).then(runNextTest));
});
});
addTest(function testLargeImage() {
// Select the image node from the test page
gWalker.querySelector(gWalker.rootNode, ".big-horizontal").then(img => {
ok(img, "Image node found in the test page");
ok(img.getImageData, "Image node has the getImageData function");
img.getImageData(100).then(imageData => {
ok(imageData.data, "Image data actor was sent back");
ok(imageData.size, "Image size info was sent back too");
is(imageData.size.naturalWidth, 5333, "Natural width of the image correct");
is(imageData.size.naturalHeight, 3000, "Natural width of the image correct");
is(imageData.size.width, 100, "Resized image width correct");
is(imageData.size.height, 56, "Resized image height correct");
ok(imageData.size.resized, "Image was resized");
imageData.data.string().then(str => {
ok(str, "We have an image data string!");
runNextTest();
});
});
});
});
addTest(function testLargeCanvas() {
// Select the canvas node from the test page
gWalker.querySelector(gWalker.rootNode, ".big-vertical").then(canvas => {
ok(canvas, "Image node found in the test page");
ok(canvas.getImageData, "Image node has the getImageData function");
canvas.getImageData(350).then(imageData => {
ok(imageData.data, "Image data actor was sent back");
ok(imageData.size, "Image size info was sent back too");
is(imageData.size.naturalWidth, 1000, "Natural width of the image correct");
is(imageData.size.naturalHeight, 2000, "Natural width of the image correct");
is(imageData.size.width, 175, "Resized image width correct");
is(imageData.size.height, 350, "Resized image height correct");
ok(imageData.size.resized, "Image was resized");
imageData.data.string().then(str => {
ok(str, "We have an image data string!");
runNextTest();
});
});
});
});
addTest(function testSmallImage() {
// Select the small image node from the test page
gWalker.querySelector(gWalker.rootNode, ".small").then(img => {
ok(img, "Image node found in the test page");
ok(img.getImageData, "Image node has the getImageData function");
img.getImageData().then(imageData => {
ok(imageData.data, "Image data actor was sent back");
ok(imageData.size, "Image size info was sent back too");
is(imageData.size.naturalWidth, 245, "Natural width of the image correct");
is(imageData.size.naturalHeight, 240, "Natural width of the image correct");
is(imageData.size.width, 245, "Resized image width correct");
is(imageData.size.height, 240, "Resized image height correct");
ok(!imageData.size.resized, "Image was NOT resized");
imageData.data.string().then(str => {
ok(str, "We have an image data string!");
runNextTest();
});
});
});
});
addTest(function cleanup() {
delete gWalker;
runNextTest();
});
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=932937">Mozilla Bug 932937</a>
<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -2201,7 +2201,7 @@ var XPIProvider = {
*/
getAddonStates: function XPI_getAddonStates(aLocation) {
let addonStates = {};
aLocation.addonLocations.forEach(function(file) {
for (let file of aLocation.addonLocations) {
let id = aLocation.getIDForLocation(file);
let unpacked = 0;
let [modFile, modTime] = recursiveLastModifiedTime(file);
@ -2219,7 +2219,8 @@ var XPIProvider = {
catch (e) { }
this._mostRecentlyModifiedFile[id] = modFile;
this.setTelemetry(id, "unpacked", unpacked);
}, this);
this.setTelemetry(id, "location", aLocation.name);
}
return addonStates;
},
@ -3182,7 +3183,7 @@ var XPIProvider = {
// The install locations are iterated in reverse order of priority so when
// there are multiple add-ons installed with the same ID the one that
// should be visible is the first one encountered.
aState.reverse().forEach(function(aSt) {
for (let aSt of aState.reverse()) {
// We can't include the install location directly in the state as it has
// to be cached as JSON.
@ -3195,7 +3196,7 @@ var XPIProvider = {
let addons = XPIDatabase.getAddonsInLocation(installLocation.name);
// Iterate through the add-ons installed the last time the application
// ran
addons.forEach(function(aOldAddon) {
for (let aOldAddon of addons) {
// If a version of this add-on has been installed in an higher
// priority install location then count it as changed
if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
@ -3213,10 +3214,25 @@ var XPIProvider = {
if (aOldAddon.visible && !aOldAddon.active)
XPIProvider.inactiveAddonIDs.push(aOldAddon.id);
// record a bit more per-addon telemetry
let loc = aOldAddon.defaultLocale;
if (loc) {
XPIProvider.setTelemetry(aOldAddon.id, "name", loc.name);
XPIProvider.setTelemetry(aOldAddon.id, "creator", loc.creator);
}
// Check if the add-on has been changed outside the XPI provider
if (aOldAddon.updateDate != addonState.mtime) {
// Did time change in the wrong direction?
if (addonState.mtime < aOldAddon.updateDate) {
this.setTelemetry(aOldAddon.id, "olderFile", {
name: this._mostRecentlyModifiedFile[aOldAddon.id],
mtime: addonState.mtime,
oldtime: aOldAddon.updateDate
});
}
// Is the add-on unpacked?
if (addonState.rdfTime) {
else if (addonState.rdfTime) {
// Was the addon manifest "install.rdf" modified, or some other file?
if (addonState.rdfTime > aOldAddon.updateDate) {
this.setTelemetry(aOldAddon.id, "modifiedInstallRDF", 1);
@ -3256,7 +3272,7 @@ var XPIProvider = {
else {
changed = removeMetadata(aOldAddon) || changed;
}
}, this);
}
}
// All the remaining add-ons in this install location must be new.
@ -3269,7 +3285,7 @@ var XPIProvider = {
changed = addMetadata(installLocation, id, addonStates[id],
locMigrateData[id]) || changed;
}
}, this);
}
// The remaining locations that had add-ons installed in them no longer
// have any add-ons installed in them, or the locations no longer exist.
@ -3277,9 +3293,9 @@ var XPIProvider = {
// database.
for (let location of knownLocations) {
let addons = XPIDatabase.getAddonsInLocation(location);
addons.forEach(function(aOldAddon) {
for (let aOldAddon of addons) {
changed = removeMetadata(aOldAddon) || changed;
}, this);
}
}
// Cache the new install location states
@ -5402,6 +5418,7 @@ AddonInstall.prototype = {
let stagedAddon = stagingDir.clone();
Task.spawn((function() {
let installedUnpacked = 0;
yield this.installLocation.requestStagingDir();
// First stage the file regardless of whether restarting is necessary
@ -5412,6 +5429,7 @@ AddonInstall.prototype = {
yield recursiveRemoveAsync(stagedAddon);
yield OS.File.makeDir(stagedAddon.path);
yield extractFilesAsync(this.file, stagedAddon);
installedUnpacked = 1;
}
else {
LOG("Addon " + this.addon.id + " will be installed as " +
@ -5546,11 +5564,18 @@ AddonInstall.prototype = {
reason, extraParams);
}
else {
// XXX this makes it dangerous to do many things in onInstallEnded
// XXX this makes it dangerous to do some things in onInstallEnded
// listeners because important cleanup hasn't been done yet
XPIProvider.unloadBootstrapScope(this.addon.id);
}
}
XPIProvider.setTelemetry(this.addon.id, "unpacked", installedUnpacked);
XPIProvider.setTelemetry(this.addon.id, "location", this.installLocation.name);
let loc = this.addon.defaultLocale;
if (loc) {
XPIProvider.setTelemetry(this.addon.id, "name", loc.name);
XPIProvider.setTelemetry(this.addon.id, "creator", loc.creator);
}
}
}).bind(this)).then(null, (e) => {
WARN("Failed to install " + this.file.path + " from " + this.sourceURI.spec, e);