Merge autoland to mozilla-central. a=merge

This commit is contained in:
Bogdan Tara 2018-07-17 00:58:15 +03:00
commit a8850882a7
124 changed files with 3396 additions and 884 deletions

1
.gitignore vendored
View File

@ -15,6 +15,7 @@ ID
# Vim swap files.
.*.sw[a-z]
.sw[a-z]
# Emacs directory variable files.
**/.dir-locals.el

View File

@ -759,6 +759,9 @@ BrowserGlue.prototype = {
// Initialize the default l10n resource sources for L10nRegistry.
let locales = Services.locale.getPackagedLocales();
const greSource = new FileSource("toolkit", locales, "resource://gre/localization/{locale}/");
L10nRegistry.registerSource(greSource);
const appSource = new FileSource("app", locales, "resource://app/localization/{locale}/");
L10nRegistry.registerSource(appSource);

View File

@ -278,3 +278,22 @@ add_task(async function window_open_form_test() {
gBrowser.removeTab(tab);
await BrowserTestUtils.closeWindow(win);
});
/**
* A test for using an IP address as the first party domain.
*/
add_task(async function ip_address_test() {
const ipAddr = "127.0.0.1";
const ipHost = `http://${ipAddr}/browser/browser/components/originattributes/test/browser/`;
let tab = BrowserTestUtils.addTab(gBrowser, ipHost + "test_firstParty.html");
await BrowserTestUtils.browserLoaded(tab.linkedBrowser, true);
await ContentTask.spawn(tab.linkedBrowser, { firstPartyDomain: ipAddr }, async function(attrs) {
info("document principal: " + content.document.nodePrincipal.origin);
Assert.equal(content.document.nodePrincipal.originAttributes.firstPartyDomain,
attrs.firstPartyDomain, "The firstPartyDomain should be set properly for the IP address");
});
gBrowser.removeTab(tab);
});

View File

@ -6,13 +6,12 @@
<ShortName>Freelang (br)</ShortName>
<Description>Geriadur Freelang</Description>
<InputEncoding>UTF-8</InputEncoding>
<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAD5+vYAQxGlAHp1DQDMzJoAnp4+AIWEDADc3b0AgH8CAJmZNADS06gAe3oAAJ6ePwCCgQUAfn0AALGxZAB/fQAAgIAAAIGAAABcP14AtrZvAIKDAACGhQsAuLhyAIqKFgDS0acAx8eSAP7+/gB7dg0AeHETALOzaACtrFsAc2ofAMLCiABPJ4MAi4oXAHh4AAB5eAAA+vv3AIGAAgB6eAAAubp2AFw/YABMIYwA9PTqAHx7AACNjBoAmJgyAH9+AABgRlUAgIEAAOjo0wCBgQAApKRKAIGAAwD7+/gAurp3AIKEAAC3t28Ag4QAAPT06wCXljAAzc2cAEUWnAB0ax4AqalVALu8egCjokgAjo4eAKurWACIhxEAhYQJALCwYwDq6tcA4+PKAJ+fQQCgn0EA+fn3AG5gLgB/fgIAyMiTAHp5AAD///8AiosPALe3cQCQkCIAfXwAAEMRpAB/fwAAgH8AAIF/AACBggAAi4sYAIKCAACPjiAA4eLGAJ2ePQChoUUA0NGkAHZtGwDJypcAh4YOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAERERERERERERLwc8LBEREREREREREREREFhYWzdVERFYJg0RERERERERES8ZNFgRWEVHClgQERERERFYYExQEVkRLWM1LxFYERFYL1NRVBERWBFYYU9DJCwsJEIaURURERFZWCdbCQAySSVRURMvERFZVUBeK0gDKEQWHmQvEREREUtRBgguBB0YXw0RWREREVg5OyNYIFFdCgsOVRERERERWzIsEQU2PS9YRkpXWBEREQ9BIhERDBcRERFSWBERERERVQtcFBERWBEzEhwRERERERFYMCpaEVkRTjEQWRERERERET8+Hyk4TT8hXBEREREREVgROgJWGwFiOlgREQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</Image>
<Url type="text/html" method="POST" template="http://www.freelang.com/enligne/breton.php?lg=fr" resultdomain="freelang.com">
<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAC8AgAAJgAAACAgAAABAAgABgYAAOICAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAKDSURBVDiNfZNLSFRRGMd/595jo811emiOSvTQKSMtKAlb5Gi4aCSKKEJ64KoWBUEQBIWBRRRBRLQpiB5UEDSLMiILKshFaGmLHlZkUUFS46OZZsKcO/d+LWYaTaH/4qzO+X/f//t+R9F6WBgvEVAKpQ2s2Ahnr3axq3kl5650sn9zNf3+fMR2QCkADCZKKeZE4pw/08GMxCgPKkpI+DycCFViOi5HrnaDOfZMTzIQoaZvkKjXw5cSH5f9PrCFF/MKMF2XQ1uXgeuCKFATDFQqRbBsPmHTIFxbBs7fdAIoHNOg+dF77iyfzfC0vH8jKMflQH2QN4ND4Ao4Qo5SFE3Ny+ZFoG3tUoZ9udmiWQO/z2JL1RIisRgAltbEDx7gWEMDynVBhDytqZ41K+0kMm4GIjRVVlFsWeCkQGtagrV4tObo4w4klaIuUM6CggIuPOsGrSd0oBQvv3+j0Ovl4c4drCgt4UZvL3WXLhFaEOB6UxMOwoXuHkTr9EgyMqlf3YrAp6EhXg8OUDt3LntqamgMBJiem8svO8nT/n4WFRZSNnMmn6NRkik7OxeVBUkyx2iS4oIZnA6tYX1FBR+GfzBi2ywp9pOrcwDY297Omc4uxDAyBhn6EOHaxg38GPnNySdP+BwZANNM9+o4hBYv5u72bSilyD92nIRto9MrVpRaFi3BVTTfbENcFzEM8HjGVqg19/v62HfvPqcaQxR5p5KIxjBAmO7x0BgoZ/et27giiKGyWI9HXJTi1UCE38kkH79Hxtb4c3SUiz3PYcqUSWT/Q6oI6xYuZFM4nL1roBQuIH+zTlIGGhH8+RZf43Ha3777z2+cXBclggn4vRYnHnekWcjoD87z7kUU/IwHAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFzUlEQVRYhbWXaWxUVRTHf/e9mc7CzBSmlC5MQYqtpSjQKQITWhUhRFCjISyS+MEVjV/co5KYaPxg/GDUxCXRmIgRYpStlppoRaKIFK0o1FppS6F2gbbTdugyr53lXT+8FmaYGVqKng/vJfeee87//O85954reOVVyRTFFImy8dg/FHVc4KPVBXS47ayqP8+9v7axq2wexwoyJ7YxVedIyfrfO3isuhGAI0Wz8LssPFf5F/bRCNWLciYXxNU4dIyGWftHJ/ZQhN0r5uLSwmhpKn/mTedw0SxCqkLFUg+BaWl8XTKbOT2DPHqwmQPe2QYbQkwdQFpU5/mKem5p6AbgVG46X/jm8mNxFl3pNkbNCiD4cE0hCEDC5qOtrGjsoXpR9rUzkBXQWHq6D4A+h4WmHCchs4l/ZjriFcejFJIdt85n37I8Tme5kkY/eQBS4k+3snd5Hu7hEPtvziNgt0ywSNCTbqPHZcUc1YkqAl0RGPRcBQBVwJLsbNoGBvj49oJx2wmGUklOQOPtT35lZ9k8vlqal7BMmcjANq8Xs6rSPRw0aBSJUVxJwqrgm8W5/FCclXQ+JQMmIdheXoYWDlPT3pG4h3Ls+BhLuFR77Hda+XxtkaEeiSTMJzIgJUjJ5oULeHDJEj6o/S3RqgRFCNbOz2ebtwSrKfVOCgE+jwctGk0eaLLBLIedd+5Yx96GBoZCofjopMSkKLy3fh0Pe71I4Pj5Lmo7zyUBKllXcD2dg4MG6iQsJc2Bx0qXkmG309IfSIr6oZLFPOz1ogjB4OgoAW0kwTFI7r6hkGyHgxNd3UntQBIGzKrKXYVGtntczoQF061Wnvb5UBUFKSX7Ghpo6e+Pc243m3ly+TJ0KXnj558xEiV5jiQwYDebSbdaAdiwYAFum/VSwgFZ0+zkOpxIKanv7uKlgwfRxyJGSlbmefhy00b8msabNTUpHadkYDgU4vzQMIUZM8lxOtm7ZTNPHKjiL38vCEHbwCAfHf8NLRLh7aPH6NU03DYrZXlzeMRbgtNq4flvq42cSFEZsSISrmMp2XrjQj7bsAFljGZ/MMiBxkYOt7bSEggQjkZxW63ku934PB5Kc3NBwls1R/n05EmGQ+FJOU8OAAkSnri5lBfLyvG4XEkXSiS9wSA17e3sqvuTPQ0NqEIhzWRCl5KRcJiIrl90k2onkgC4BMLjcrImP5/S3BxynU5UodCnaZwNBDjeeY6hcIiCDDcl2TkUZ87EbbNjUVV0JMOhMM19fXx7+jT7/v6b/pERkqFIASBZyBIEFLjd3Ft0A/fftIjiWZmoIj6PRQz1EV1HFYI+TeOF6mp2nDxJRI93N+nbsDDDzbM+31hl2OgOBvn+zBka/b34g0Eius50q5WizExWeGaTYbOjCoEWiZBht/Pu+vVYTSbeq62NY+LKAKQk2zGNZ3w+Hi8tpSUQ4K2aGqqamqjr6kaXumFsPOqxck1TVTYVF7O9vIwFmZnoUmIxmXh9zWo+r6+nN+bgSg1ASm6dO5f371yHP6ixZc8eDre2MhQKG/NCwGX0jwMJ6To76+o4dPYMFVvvozQnl6iUONIslM+Zw/5TjVcAICWqovDcSh9bFt7Ii98dpKqpGd248iZdXghB5+AQj1ZUcujBB0i3GA1Mhs0Wp5ZwEpoUhddW3cb8GTO4c+dOKhubxk66yfcAsSDqerqp7Wi/ONQ+MJACgJHkbC8vo3t4iG2VVZwbGo7xO4Xng5REdZ3msbviwsgIR9ra4lRM44oA9xQW0nZhgB0nTlyMwPhfve9YcaVZQEp21dUZOSRiqyAmsB/OttI/Ohrv/BrFZbGwODublkCA1386kmDXFNtg9l/efPwHstLjIX/GdLbu3kP74ACX0zlhU3otkqaqvFBexsvfH6IipvRiZepvw8tFjn+MLlUIeGr5MipPNfLOsV+u8WEyeQQX/6uuu476nh6qmpqN4f8fwCXJsNlo6u2lbWBwQt1/AUtVOfRNAUAmAAAAAElFTkSuQmCC</Image>
<Url type="text/html" method="POST" template="https://www.freelang.com/enligne/breton.php" resultdomain="freelang.com" rel="searchform">
<Param name="dico" value="fr_bre_fra"/>
<Param name="lg" value="fr"/>
<Param name="mot1" value="{searchTerms}"/>
<Param name="mot2" value=""/>
<Param name="entier" value="on"/>
</Url>
<SearchForm>http://www.freelang.com/enligne/breton.php</SearchForm>
</SearchPlugin>

View File

@ -4,7 +4,6 @@
"use strict";
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
ChromeUtils.defineModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
@ -15,72 +14,6 @@ XPCOMUtils.defineLazyServiceGetter(this, "resProto",
const RESOURCE_HOST = "activity-stream";
const BROWSER_READY_NOTIFICATION = "sessionstore-windows-restored";
const RESOURCE_BASE = "resource://activity-stream";
let activityStream;
let modulesToUnload = new Set();
let waitingForBrowserReady = true;
// Lazily load ActivityStream then find related modules to unload
XPCOMUtils.defineLazyModuleGetter(this, "ActivityStream",
"resource://activity-stream/lib/ActivityStream.jsm", null, null, () => {
// Helper to fetch a resource directory listing and call back with each item
const processListing = async (uri, cb) => {
try {
(await (await fetch(uri)).text())
.split("\n").slice(2).forEach(line => cb(line.split(" ").slice(1)));
} catch (e) {
// Silently ignore listings that fail to load
// probably because the resource: has been unloaded
}
};
// Look for modules one level deeper than the top resource URI
processListing(RESOURCE_BASE, ([directory, , , type]) => {
if (type === "DIRECTORY") {
// Look into this directory for .jsm files
const subDir = `${RESOURCE_BASE}/${directory}`;
processListing(subDir, ([name]) => {
if (name && name.search(/\.jsm$/) !== -1) {
modulesToUnload.add(`${subDir}/${name}`);
}
});
}
});
});
/**
* init - Initializes an instance of ActivityStream. This could be called by
* the startup() function exposed by bootstrap.js.
*/
function init() {
// Don't re-initialize
if (activityStream && activityStream.initialized) {
return;
}
activityStream = new ActivityStream();
try {
activityStream.init();
} catch (e) {
Cu.reportError(e);
}
}
/**
* uninit - Uninitializes the activityStream instance, if it exsits.This could be
* called by the shutdown() function exposed by bootstrap.js.
*
* @param {type} reason Reason for uninitialization. Could be uninstall, upgrade, or PREF_OFF
*/
function uninit(reason) {
// Make sure to only uninit once in case both pref change and shutdown happen
if (activityStream) {
activityStream.uninit(reason);
activityStream = null;
}
}
/**
* Check if an old pref has a custom value to migrate. Clears the pref so that
* it's the default after migrating (to avoid future need to migrate).
@ -113,12 +46,14 @@ function migratePref(oldPrefName, cbIfNotDefault) {
Services.prefs.clearUserPref(oldPrefName);
}
/**
* onBrowserReady - Continues startup of the add-on after browser is ready.
*/
function onBrowserReady() {
waitingForBrowserReady = false;
init();
// The functions below are required by bootstrap.js
this.install = function install(data, reason) {};
this.startup = function startup(data, reason) {
resProto.setSubstitutionWithFlags(RESOURCE_HOST,
Services.io.newURI("chrome/content/", null, data.resourceURI),
resProto.ALLOW_CONTENT_ACCESS);
// Do a one time migration of Tiles about:newtab prefs that have been modified
migratePref("browser.newtabpage.rows", rows => {
@ -140,57 +75,10 @@ function onBrowserReady() {
migratePref("browser.newtabpage.activity-stream.topSitesCount", count => {
Services.prefs.setIntPref("browser.newtabpage.activity-stream.topSitesRows", Math.ceil(count / 6));
});
}
/**
* observe - nsIObserver callback to handle various browser notifications.
*/
function observe(subject, topic, data) {
switch (topic) {
case BROWSER_READY_NOTIFICATION:
Services.obs.removeObserver(observe, BROWSER_READY_NOTIFICATION);
// Avoid running synchronously during this event that's used for timing
Services.tm.dispatchToMainThread(() => onBrowserReady());
break;
}
}
// The functions below are required by bootstrap.js
this.install = function install(data, reason) {};
this.startup = function startup(data, reason) {
resProto.setSubstitutionWithFlags(RESOURCE_HOST,
Services.io.newURI("chrome/content/", null, data.resourceURI),
resProto.ALLOW_CONTENT_ACCESS);
// Only start Activity Stream up when the browser UI is ready
if (Services.startup.startingUp) {
Services.obs.addObserver(observe, BROWSER_READY_NOTIFICATION);
} else {
// Handle manual install or automatic install after manual uninstall
onBrowserReady();
}
};
this.shutdown = function shutdown(data, reason) {
resProto.setSubstitution(RESOURCE_HOST, null);
// Uninitialize Activity Stream
uninit(reason);
// Stop waiting for browser to be ready
if (waitingForBrowserReady) {
Services.obs.removeObserver(observe, BROWSER_READY_NOTIFICATION);
}
// Unload any add-on modules that might might have been imported
modulesToUnload.forEach(Cu.unload);
};
this.uninstall = function uninstall(data, reason) {
if (activityStream) {
activityStream.uninstall(reason);
activityStream = null;
}
};
this.uninstall = function uninstall(data, reason) {};

View File

@ -9,43 +9,74 @@ var EXPORTED_SYMBOLS = [ "AboutNewTab" ];
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "AutoMigrate",
"resource:///modules/AutoMigrate.jsm");
ChromeUtils.defineModuleGetter(this, "NewTabUtils",
"resource://gre/modules/NewTabUtils.jsm");
ChromeUtils.defineModuleGetter(this, "RemotePages",
"resource://gre/modules/RemotePageManager.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
ActivityStream: "resource://activity-stream/lib/ActivityStream.jsm",
RemotePages: "resource://gre/modules/RemotePageManager.jsm"
});
const BROWSER_READY_NOTIFICATION = "sessionstore-windows-restored";
var AboutNewTab = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
// AboutNewTab
pageListener: null,
isOverridden: false,
activityStream: null,
/**
* init - Initializes an instance of Activity Stream if one doesn't exist already
* and creates the instance of Remote Page Manager which Activity Stream
* uses for message passing.
*
* @param {obj} pageListener - Optional argument. An existing instance of RemotePages
* which Activity Stream has previously made, and we
* would like to re-use.
*/
init(pageListener) {
if (this.isOverridden) {
return;
}
// Since `init` can be called via `reset` at a later time with an existing
// pageListener, we want to only add the observer if we are initializing
// without this pageListener argument. This means it was the first call to `init`
if (!pageListener) {
Services.obs.addObserver(this, BROWSER_READY_NOTIFICATION);
}
this.pageListener = pageListener || new RemotePages(["about:home", "about:newtab", "about:welcome"]);
this.pageListener.addMessageListener("NewTab:Customize", this.customize);
this.pageListener.addMessageListener("NewTab:MaybeShowMigrateMessage",
this.maybeShowMigrateMessage);
},
maybeShowMigrateMessage({ target }) {
AutoMigrate.shouldShowMigratePrompt(target.browser).then((prompt) => {
if (prompt) {
AutoMigrate.showUndoNotificationBar(target.browser);
}
});
},
customize(message) {
NewTabUtils.allPages.enabled = message.data.enabled;
NewTabUtils.allPages.enhanced = message.data.enhanced;
/**
* onBrowserReady - Continues the initialization of Activity Stream after browser is ready.
*/
onBrowserReady() {
if (this.activityStream && this.activityStream.initialized) {
return;
}
this.activityStream = new ActivityStream();
try {
this.activityStream.init();
} catch (e) {
Cu.reportError(e);
}
},
/**
* uninit - Uninitializes Activity Stream if it exists, and destroys the pageListener
* if it exists.
*/
uninit() {
if (this.activityStream) {
this.activityStream.uninit();
this.activityStream = null;
}
if (this.pageListener) {
this.pageListener.destroy();
this.pageListener = null;
@ -59,9 +90,6 @@ var AboutNewTab = {
return null;
if (shouldPassPageListener) {
this.pageListener = null;
pageListener.removeMessageListener("NewTab:Customize", this.customize);
pageListener.removeMessageListener("NewTab:MaybeShowMigrateMessage",
this.maybeShowMigrateMessage);
return pageListener;
}
this.uninit();
@ -71,5 +99,17 @@ var AboutNewTab = {
reset(pageListener) {
this.isOverridden = false;
this.init(pageListener);
},
// nsIObserver implementation
observe(subject, topic, data) {
switch (topic) {
case BROWSER_READY_NOTIFICATION:
Services.obs.removeObserver(this, BROWSER_READY_NOTIFICATION);
// Avoid running synchronously during this event that's used for timing
Services.tm.dispatchToMainThread(() => this.onBrowserReady());
break;
}
}
};

View File

@ -11,6 +11,7 @@
#include "nsIEffectiveTLDService.h"
#include "nsIURI.h"
#include "nsIURIWithPrincipal.h"
#include "nsURLHelper.h"
namespace mozilla {
@ -58,6 +59,28 @@ OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument,
return;
}
if (rv == NS_ERROR_HOST_IS_IP_ADDRESS) {
// If the host is an IPv4/IPv6 address, we still accept it as a
// valid firstPartyDomain.
nsAutoCString ipAddr;
rv = aURI->GetHost(ipAddr);
NS_ENSURE_SUCCESS_VOID(rv);
if (net_IsValidIPv6Addr(ipAddr.BeginReading(), ipAddr.Length())) {
// According to RFC2732, the host of an IPv6 address should be an
// IPv6reference. The GetHost() of nsIURI will only return the IPv6
// address. So, we need to convert it back to IPv6reference here.
mFirstPartyDomain.Truncate();
mFirstPartyDomain.AssignLiteral("[");
mFirstPartyDomain.Append(NS_ConvertUTF8toUTF16(ipAddr));
mFirstPartyDomain.AppendLiteral("]");
} else {
mFirstPartyDomain = NS_ConvertUTF8toUTF16(ipAddr);
}
return;
}
nsAutoCString scheme;
rv = aURI->GetScheme(scheme);
NS_ENSURE_SUCCESS_VOID(rv);

View File

@ -65,8 +65,13 @@ pref("devtools.inspector.shapesHighlighter.enabled", true);
pref("devtools.flexboxinspector.enabled", false);
// Enable the new Animation Inspector
pref("devtools.new-animationinspector.enabled", true);
// Enable the Variable Fonts editor
// Enable the Variable Fonts editor only in Nightly
#if defined(NIGHTLY_BUILD)
pref("devtools.inspector.fonteditor.enabled", true);
#else
pref("devtools.inspector.fonteditor.enabled", false);
#endif
// Enable the font highlight-on-hover feature
pref("devtools.inspector.fonthighlighter.enabled", false);

View File

@ -28,6 +28,7 @@ const MAX_VERTICAL_OFFSET = 3;
// line in text selection.
const RE_SCRATCHPAD_ERROR = /(?:@Scratchpad\/\d+:|\()(\d+):?(\d+)?(?:\)|\n)/;
const RE_JUMP_TO_LINE = /^(\d+):?(\d+)?/;
const AUTOCOMPLETE_MARK_CLASSNAME = "cm-auto-complete-shadow-text";
const Services = require("Services");
const EventEmitter = require("devtools/shared/event-emitter");
@ -58,22 +59,23 @@ const CM_SCRIPTS = [
const CM_IFRAME = "chrome://devtools/content/sourceeditor/codemirror/cmiframe.html";
const CM_MAPPING = [
"clearHistory",
"defaultCharWidth",
"extendSelection",
"focus",
"getCursor",
"getScrollInfo",
"getSelection",
"getViewport",
"hasFocus",
"lineCount",
"somethingSelected",
"getCursor",
"setSelection",
"getSelection",
"replaceSelection",
"extendSelection",
"undo",
"redo",
"clearHistory",
"openDialog",
"redo",
"refresh",
"getScrollInfo",
"getViewport"
"replaceSelection",
"setSelection",
"somethingSelected",
"undo",
];
const editors = new WeakMap();
@ -179,6 +181,16 @@ function Editor(config) {
// indenting with tabs, insert one tab. Otherwise insert N
// whitespaces where N == indentUnit option.
this.config.extraKeys.Tab = cm => {
if (config.extraKeys && config.extraKeys.Tab) {
// If a consumer registers its own extraKeys.Tab, we execute it before doing
// anything else. If it returns false, that mean that all the key handling work is
// done, so we can do an early return.
const res = config.extraKeys.Tab(cm);
if (res === false) {
return;
}
}
if (cm.somethingSelected()) {
cm.indentSelection("add");
return;
@ -1262,6 +1274,32 @@ Editor.prototype = {
}
},
getAutoCompletionText() {
const cm = editors.get(this);
const mark = cm.getAllMarks().find(m => m.className === AUTOCOMPLETE_MARK_CLASSNAME);
if (!mark) {
return "";
}
return mark.title || "";
},
setAutoCompletionText: function(text) {
const cursor = this.getCursor();
const cm = editors.get(this);
const className = AUTOCOMPLETE_MARK_CLASSNAME;
cm.getAllMarks().forEach(mark => {
if (mark.className === className) {
mark.clear();
}
});
if (text) {
cm.markText({...cursor, ch: cursor.ch - 1}, cursor, { className, title: text });
}
},
/**
* Extends an instance of the Editor object with additional
* functions. Each function will be called with context as

View File

@ -382,6 +382,11 @@ textarea.jsterm-input-node:focus {
border-bottom-right-radius: 0;
}
.jsterm-cm .cm-auto-complete-shadow-text::after {
content: attr(title);
color: var(--theme-comment);
}
/* Security styles */
.message.security > .indent {

View File

@ -170,14 +170,6 @@ class JSTerm extends Component {
// such as the browser console which doesn't have a toolbox.
this.autocompletePopup = new AutocompletePopup(tooltipDoc, autocompleteOptions);
this.inputBorderSize = this.inputNode
? this.inputNode.getBoundingClientRect().height - this.inputNode.clientHeight
: 0;
// Update the character width and height needed for the popup offset
// calculations.
this._updateCharSize();
if (this.props.codeMirrorEnabled) {
if (this.node) {
this.editor = new Editor({
@ -190,17 +182,51 @@ class JSTerm extends Component {
tabIndex: "0",
viewportMargin: Infinity,
extraKeys: {
"Enter": (e, cm) => {
if (!this.autocompletePopup.isOpen && (
e.shiftKey || !Debugger.isCompilableUnit(this.getInputValue())
)) {
// shift return or incomplete statement
"Enter": () => {
// No need to handle shift + Enter as it's natively handled by CodeMirror.
if (
!this.autocompletePopup.isOpen &&
!Debugger.isCompilableUnit(this.getInputValue())
) {
// incomplete statement
return "CodeMirror.Pass";
}
if (this._autocompletePopupNavigated &&
this.autocompletePopup.isOpen &&
this.autocompletePopup.selectedIndex > -1
) {
return this.acceptProposedCompletion();
}
this.execute();
return null;
},
"Tab": () => {
// Generate a completion and accept the first proposed value.
if (
this.complete(this.COMPLETE_HINT_ONLY) &&
this.lastCompletion &&
this.acceptProposedCompletion()
) {
return false;
}
if (this.hasEmptyInput()) {
this.editor.codeMirror.getInputField().blur();
return false;
}
if (!this.editor.somethingSelected()) {
this.insertStringAtCursor("\t");
return false;
}
// Input is not empty and some text is selected, let the editor handle this.
return true;
},
"Up": () => {
let inputUpdated;
if (this.autocompletePopup.isOpen) {
@ -217,6 +243,7 @@ class JSTerm extends Component {
}
return null;
},
"Down": () => {
let inputUpdated;
if (this.autocompletePopup.isOpen) {
@ -234,6 +261,36 @@ class JSTerm extends Component {
return null;
},
"Left": () => {
if (this.autocompletePopup.isOpen || this.lastCompletion.value) {
this.clearCompletion();
}
return "CodeMirror.Pass";
},
"Right": () => {
const haveSuggestion =
this.autocompletePopup.isOpen || this.lastCompletion.value;
const useCompletion =
this.canCaretGoNext() || this._autocompletePopupNavigated;
if (
haveSuggestion &&
useCompletion &&
this.complete(this.COMPLETE_HINT_ONLY) &&
this.lastCompletion.value &&
this.acceptProposedCompletion()
) {
return null;
}
if (this.autocompletePopup.isOpen) {
this.clearCompletion();
}
return "CodeMirror.Pass";
},
"Ctrl-N": () => {
// Control-N differs from down arrow: it ignores autocomplete state.
// Note that we preserve the default 'down' navigation within
@ -251,8 +308,8 @@ class JSTerm extends Component {
},
"Ctrl-P": () => {
// Control-N differs from down arrow: it ignores autocomplete state.
// Note that we preserve the default 'down' navigation within
// Control-P differs from up arrow: it ignores autocomplete state.
// Note that we preserve the default 'up' navigation within
// multiline text.
if (
Services.appinfo.OS === "Darwin"
@ -263,11 +320,62 @@ class JSTerm extends Component {
}
this.clearCompletion();
return "CodeMirror.Pass";
},
"Esc": () => {
if (this.autocompletePopup.isOpen) {
this.clearCompletion();
return null;
}
return "CodeMirror.Pass";
},
"PageUp": () => {
if (this.autocompletePopup.isOpen) {
if (this.complete(this.COMPLETE_PAGEUP)) {
this._autocompletePopupNavigated = true;
}
return null;
}
return "CodeMirror.Pass";
},
"PageDown": () => {
if (this.autocompletePopup.isOpen) {
if (this.complete(this.COMPLETE_PAGEDOWN)) {
this._autocompletePopupNavigated = true;
}
return null;
}
return "CodeMirror.Pass";
},
"Home": () => {
if (this.autocompletePopup.isOpen) {
this.autocompletePopup.selectedIndex = 0;
return null;
}
return "CodeMirror.Pass";
},
"End": () => {
if (this.autocompletePopup.isOpen) {
this.autocompletePopup.selectedIndex =
this.autocompletePopup.itemCount - 1;
return null;
}
return "CodeMirror.Pass";
}
}
});
this.editor.on("change", this._inputEventHandler);
this.editor.appendToLocalElement(this.node);
const cm = this.editor.codeMirror;
cm.on("paste", (_, event) => this.props.onPaste(event));
@ -280,6 +388,14 @@ class JSTerm extends Component {
this.focus();
}
this.inputBorderSize = this.inputNode
? this.inputNode.getBoundingClientRect().height - this.inputNode.clientHeight
: 0;
// Update the character and chevron width needed for the popup offset calculations.
this._inputCharWidth = this._getInputCharWidth();
this._chevronWidth = this.editor ? null : this._getChevronWidth();
this.hud.window.addEventListener("blur", this._blurEventHandler);
this.lastInputValue && this.setInputValue(this.lastInputValue);
}
@ -576,12 +692,13 @@ class JSTerm extends Component {
* The new value to set.
* @returns void
*/
setInputValue(newValue) {
setInputValue(newValue = "") {
if (this.props.codeMirrorEnabled) {
if (this.editor) {
this.editor.setText(newValue);
// Set the cursor at the end of the input.
this.editor.setCursor({line: this.editor.getDoc().lineCount(), ch: 0});
this.editor.setAutoCompletionText();
}
} else {
if (!this.inputNode) {
@ -603,21 +720,30 @@ class JSTerm extends Component {
*/
getInputValue() {
if (this.props.codeMirrorEnabled) {
return this.editor.getText() || "";
return this.editor ? this.editor.getText() || "" : "";
}
return this.inputNode ? this.inputNode.value || "" : "";
}
getSelectionStart() {
if (this.props.codeMirrorEnabled) {
return this.getInputValueBeforeCursor().length;
}
return this.inputNode ? this.inputNode.selectionStart : null;
}
/**
* The inputNode "input" and "keyup" event handler.
* @private
*/
_inputEventHandler() {
if (this.lastInputValue != this.getInputValue()) {
const value = this.getInputValue();
if (this.lastInputValue !== value) {
this.resizeInput();
this.complete(this.COMPLETE_HINT_ONLY);
this.lastInputValue = this.getInputValue();
this.lastInputValue = value;
}
}
@ -642,7 +768,6 @@ class JSTerm extends Component {
const inputNode = this.inputNode;
const inputValue = this.getInputValue();
let inputUpdated = false;
if (event.ctrlKey) {
switch (event.charCode) {
case 101:
@ -999,10 +1124,8 @@ class JSTerm extends Component {
* or false otherwise.
*/
complete(type, callback) {
const inputNode = this.inputNode;
const inputValue = this.getInputValue();
const frameActor = this.getFrameActor(this.SELECTED_FRAME);
// If the inputNode has no value, then don't try to complete on it.
if (!inputValue) {
this.clearCompletion();
@ -1011,8 +1134,12 @@ class JSTerm extends Component {
return false;
}
const {editor, inputNode} = this;
// Only complete if the selection is empty.
if (inputNode.selectionStart != inputNode.selectionEnd) {
if (
(inputNode && inputNode.selectionStart != inputNode.selectionEnd) ||
(editor && editor.getSelection())
) {
this.clearCompletion();
this.callback && callback(this);
this.emit("autocomplete-updated");
@ -1020,8 +1147,7 @@ class JSTerm extends Component {
}
// Update the completion results.
if (this.lastCompletion.value != inputValue ||
frameActor != this._lastFrameActorId) {
if (this.lastCompletion.value != inputValue || frameActor != this._lastFrameActorId) {
this._updateCompletionResult(type, callback);
return false;
}
@ -1058,20 +1184,20 @@ class JSTerm extends Component {
* Optional, function to invoke when completion results are received.
*/
_updateCompletionResult(type, callback) {
const value = this.getInputValue();
const frameActor = this.getFrameActor(this.SELECTED_FRAME);
if (this.lastCompletion.value == this.getInputValue() &&
frameActor == this._lastFrameActorId) {
if (this.lastCompletion.value == value && frameActor == this._lastFrameActorId) {
return;
}
const requestId = gSequenceId();
const cursor = this.inputNode.selectionStart;
const input = this.getInputValue().substring(0, cursor);
const cursor = this.getSelectionStart();
const input = value.substring(0, cursor);
const cache = this._autocompleteCache;
// If the current input starts with the previous input, then we already
// have a list of suggestions and we just need to filter the cached
// suggestions. When the current input ends with a non-alphanumeri;
// suggestions. When the current input ends with a non-alphanumeric
// character we ask the server again for suggestions.
// Check if last character is non-alphanumeric
@ -1079,7 +1205,6 @@ class JSTerm extends Component {
this._autocompleteQuery = null;
this._autocompleteCache = null;
}
if (this._autocompleteQuery && input.startsWith(this._autocompleteQuery)) {
let filterBy = input;
// Find the last non-alphanumeric other than _ or $ if it exists.
@ -1090,9 +1215,7 @@ class JSTerm extends Component {
filterBy = input.substring(input.lastIndexOf(lastNonAlpha) + 1);
}
const newList = cache.sort().filter(function(l) {
return l.startsWith(filterBy);
});
const newList = cache.sort().filter(l => l.startsWith(filterBy));
this.lastCompletion = {
requestId: null,
@ -1104,7 +1227,6 @@ class JSTerm extends Component {
this._receiveAutocompleteProperties(null, callback, response);
return;
}
this._lastFrameActorId = frameActor;
this.lastCompletion = {
@ -1120,6 +1242,19 @@ class JSTerm extends Component {
input, cursor, autocompleteCallback, frameActor);
}
getInputValueBeforeCursor() {
if (this.editor) {
return this.editor.getDoc().getRange({line: 0, ch: 0}, this.editor.getCursor());
}
if (this.inputNode) {
const cursor = this.inputNode.selectionStart;
return this.getInputValue().substring(0, cursor);
}
return null;
}
/**
* Handler for the autocompletion results. This method takes
* the completion result received from the server and updates the UI
@ -1134,7 +1269,6 @@ class JSTerm extends Component {
* the content process.
*/
_receiveAutocompleteProperties(requestId, callback, message) {
const inputNode = this.inputNode;
const inputValue = this.getInputValue();
if (this.lastCompletion.value == inputValue ||
requestId != this.lastCompletion.requestId) {
@ -1142,8 +1276,7 @@ class JSTerm extends Component {
}
// Cache whatever came from the server if the last char is
// alphanumeric or '.'
const cursor = inputNode.selectionStart;
const inputUntilCursor = inputValue.substring(0, cursor);
const inputUntilCursor = this.getInputValueBeforeCursor();
if (requestId != null && /[a-zA-Z0-9.]$/.test(inputUntilCursor)) {
this._autocompleteCache = message.matches;
@ -1159,10 +1292,7 @@ class JSTerm extends Component {
return;
}
const items = matches.reverse().map(function(match) {
return { preLabel: lastPart, label: match };
});
const items = matches.reverse().map(match => ({ preLabel: lastPart, label: match }));
const popup = this.autocompletePopup;
popup.setItems(items);
@ -1172,11 +1302,27 @@ class JSTerm extends Component {
matchProp: lastPart,
};
if (items.length > 1 && !popup.isOpen) {
const str = this.getInputValue().substr(0, this.inputNode.selectionStart);
const offset = str.length - (str.lastIndexOf("\n") + 1) - lastPart.length;
const x = offset * this._inputCharWidth;
popup.openPopup(inputNode, x + this._chevronWidth);
this._autocompletePopupNavigated = false;
let popupAlignElement;
let xOffset;
let yOffset;
if (this.editor) {
popupAlignElement = this.node.querySelector(".CodeMirror-cursor");
// We need to show the popup at the ".".
xOffset = -1 * lastPart.length * this._inputCharWidth;
yOffset = 4;
} else if (this.inputNode) {
const offset = inputUntilCursor.length -
(inputUntilCursor.lastIndexOf("\n") + 1) -
lastPart.length;
xOffset = (offset * this._inputCharWidth) + this._chevronWidth;
popupAlignElement = this.inputNode;
}
if (popupAlignElement) {
popup.openPopup(popupAlignElement, xOffset, yOffset);
this._autocompletePopupNavigated = false;
}
} else if (items.length < 2 && popup.isOpen) {
popup.hidePopup();
this._autocompletePopupNavigated = false;
@ -1201,7 +1347,7 @@ class JSTerm extends Component {
onAutocompleteSelect() {
// Render the suggestion only if the cursor is at the end of the input.
if (this.inputNode.selectionStart != this.getInputValue().length) {
if (this.getSelectionStart() != this.getInputValue().length) {
return;
}
@ -1228,9 +1374,11 @@ class JSTerm extends Component {
if (this.autocompletePopup.isOpen) {
// Trigger a blur/focus of the JSTerm input to force screen readers to read the
// value again.
this.inputNode.blur();
if (this.inputNode) {
this.inputNode.blur();
}
this.autocompletePopup.once("popup-closed", () => {
this.inputNode.focus();
this.focus();
});
this.autocompletePopup.hidePopup();
this._autocompletePopupNavigated = false;
@ -1253,6 +1401,7 @@ class JSTerm extends Component {
this.insertStringAtCursor(
currentItem.label.substring(this.lastCompletion.matchProp.length)
);
updated = true;
}
@ -1268,12 +1417,25 @@ class JSTerm extends Component {
* @param string str
*/
insertStringAtCursor(str) {
const cursor = this.inputNode.selectionStart;
const value = this.getInputValue();
this.setInputValue(value.substr(0, cursor) +
str + value.substr(cursor));
const newCursor = cursor + str.length;
this.inputNode.selectionStart = this.inputNode.selectionEnd = newCursor;
const prefix = this.getInputValueBeforeCursor();
const suffix = value.replace(prefix, "");
// We need to retrieve the cursor before setting the new value.
const editorCursor = this.editor && this.editor.getCursor();
this.setInputValue(prefix + str + suffix);
if (this.inputNode) {
const newCursor = prefix.length + str.length;
this.inputNode.selectionStart = this.inputNode.selectionEnd = newCursor;
} else if (this.editor) {
// Set the cursor on the same line it was already at, after the autocompleted text
this.editor.setCursor({
line: editorCursor.line,
ch: editorCursor.ch + str.length
});
}
}
/**
@ -1283,23 +1445,30 @@ class JSTerm extends Component {
* The proposed suffix for the inputNode value.
*/
updateCompleteNode(suffix) {
if (!this.completeNode) {
return;
if (this.completeNode) {
// completion prefix = input, with non-control chars replaced by spaces
const prefix = suffix ? this.getInputValue().replace(/[\S]/g, " ") : "";
this.completeNode.value = prefix + suffix;
}
// completion prefix = input, with non-control chars replaced by spaces
const prefix = suffix ? this.getInputValue().replace(/[\S]/g, " ") : "";
this.completeNode.value = prefix + suffix;
if (this.editor) {
this.editor.setAutoCompletionText(suffix);
}
}
/**
* Calculates the width and height of a single character of the input box.
* Calculates and returns the width of a single character of the input box.
* This will be used in opening the popup at the correct offset.
*
* @private
* @returns {Number|null}: Width off the "x" char, or null if the input does not exist.
*/
_updateCharSize() {
if (this.props.codeMirrorEnabled || !this.inputNode) {
return;
_getInputCharWidth() {
if (!this.inputNode && !this.node) {
return null;
}
if (this.editor) {
return this.editor.defaultCharWidth();
}
const doc = this.hud.document;
@ -1313,12 +1482,28 @@ class JSTerm extends Component {
WebConsoleUtils.copyTextStyles(this.inputNode, tempLabel);
tempLabel.textContent = "x";
doc.documentElement.appendChild(tempLabel);
this._inputCharWidth = tempLabel.offsetWidth;
const width = tempLabel.offsetWidth;
tempLabel.remove();
// Calculate the width of the chevron placed at the beginning of the input
return width;
}
/**
* Calculates and returns the width of the chevron icon.
* This will be used in opening the popup at the correct offset.
*
* @returns {Number|null}: Width of the icon, or null if the input does not exist.
*/
_getChevronWidth() {
if (!this.inputNode) {
return null;
}
// Calculate the width of the chevron placed at the beginning of the input
// box. Remove 4 more pixels to accommodate the padding of the popup.
this._chevronWidth = +doc.defaultView.getComputedStyle(this.inputNode)
.paddingLeft.replace(/[^0-9.]/g, "") - 4;
const doc = this.hud.document;
return doc.defaultView
.getComputedStyle(this.inputNode)
.paddingLeft.replace(/[^0-9.]/g, "") - 4;
}
destroy() {

View File

@ -184,6 +184,7 @@ skip-if = verify
[browser_jsterm_accessibility.js]
[browser_jsterm_add_edited_input_to_history.js]
[browser_jsterm_autocomplete_array_no_index.js]
[browser_jsterm_autocomplete_arrow_keys.js]
[browser_jsterm_autocomplete_cached_results.js]
[browser_jsterm_autocomplete_crossdomain_iframe.js]
[browser_jsterm_autocomplete_escape_key.js]

View File

@ -12,17 +12,18 @@
const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 817834";
add_task(async function() {
const hud = await openNewTabAndConsole(TEST_URI);
await testEditedInputHistory(hud);
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function testEditedInputHistory(hud) {
const jsterm = hud.jsterm;
const inputNode = jsterm.inputNode;
async function performTests() {
const {jsterm} = await openNewTabAndConsole(TEST_URI);
ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
is(inputNode.selectionStart, 0);
is(inputNode.selectionEnd, 0);
checkJsTermCursor(jsterm, 0, "Cursor is at expected position");
jsterm.setInputValue('"first item"');
EventUtils.synthesizeKey("KEY_ArrowUp");

View File

@ -10,6 +10,14 @@
const TEST_URI = "data:text/html;charset=utf8,test autocompletion with $ or _";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const { jsterm } = await openNewTabAndConsole(TEST_URI);
await jsterm.execute("var testObject = {$$aaab: '', $$aaac: ''}");
@ -28,7 +36,7 @@ add_task(async function() {
await testAutocomplete(jsterm, "blargh");
await testAutocomplete(jsterm, "foobar.a");
await testAutocomplete(jsterm, "blargh.a");
});
}
async function testAutocomplete(jsterm, inputString) {
jsterm.setInputValue(inputString);

View File

@ -16,6 +16,14 @@ const TEST_URI = `data:text/html;charset=utf-8,
<body>bug 585991 - Autocomplete popup on array</body>`;
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const { jsterm } = await openNewTabAndConsole(TEST_URI);
const {
@ -38,4 +46,4 @@ add_task(async function() {
EventUtils.synthesizeKey("KEY_Escape");
await onPopupClose;
});
}

View File

@ -0,0 +1,69 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const TEST_URI = `data:text/html;charset=utf-8,<head><script>
/* Create a prototype-less object so popup does not contain native
* Object prototype properties.
*/
window.foo = Object.create(null, Object.getOwnPropertyDescriptors({
aa: "a",
bb: "b",
}));
</script></head><body>Autocomplete text navigation key usage test</body>`;
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const { jsterm } = await openNewTabAndConsole(TEST_URI);
const { autocompletePopup: popup } = jsterm;
const checkInput = (expected, assertionInfo) =>
checkJsTermValueAndCursor(jsterm, expected, assertionInfo);
let onPopUpOpen = popup.once("popup-opened");
jsterm.setInputValue("window.foo");
EventUtils.sendString(".");
await onPopUpOpen;
info("Trigger autocomplete popup opening");
// checkInput is asserting the cursor position with the "|" char.
checkInput("window.foo.|");
is(popup.isOpen, true, "popup is open");
checkJsTermCompletionValue(jsterm, " aa", "completeNode has expected value");
info("Test that arrow left closes the popup and clears complete node");
let onPopUpClose = popup.once("popup-closed");
EventUtils.synthesizeKey("KEY_ArrowLeft");
await onPopUpClose;
checkInput("window.foo|.");
is(popup.isOpen, false, "popup is closed");
checkJsTermCompletionValue(jsterm, "", "completeNode is empty");
info("Trigger autocomplete popup opening again");
onPopUpOpen = popup.once("popup-opened");
jsterm.setInputValue("window.foo");
EventUtils.sendString(".");
await onPopUpOpen;
checkInput("window.foo.|");
is(popup.isOpen, true, "popup is open");
checkJsTermCompletionValue(jsterm, " aa", "completeNode has expected value");
info("Test that arrow right selects selected autocomplete item");
onPopUpClose = popup.once("popup-closed");
EventUtils.synthesizeKey("KEY_ArrowRight");
await onPopUpClose;
checkInput("window.foo.aa|");
is(popup.isOpen, false, "popup is closed");
checkJsTermCompletionValue(jsterm, "", "completeNode is empty");
}

View File

@ -11,38 +11,58 @@
const TEST_URI = "data:text/html;charset=utf8,<p>test cached autocompletion results";
add_task(async function() {
const { jsterm } = await openNewTabAndConsole(TEST_URI);
const {
autocompletePopup: popup,
completeNode,
inputNode: input,
} = jsterm;
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
const jstermComplete = (value, offset) =>
jstermSetValueAndComplete(jsterm, value, offset);
async function performTests() {
const { jsterm } = await openNewTabAndConsole(TEST_URI);
const { autocompletePopup: popup } = jsterm;
const jstermComplete = (value, caretPosition) =>
jstermSetValueAndComplete(jsterm, value, caretPosition);
// Test if 'doc' gives 'document'
await jstermComplete("doc");
is(input.value, "doc", "'docu' completion (input.value)");
is(completeNode.value, " ument", "'docu' completion (completeNode)");
is(jsterm.getInputValue(), "doc", "'docu' completion (input.value)");
checkJsTermCompletionValue(jsterm, " ument", "'docu' completion (completeNode)");
// Test typing 'window.'.
// Test typing 'window.'.'
await jstermComplete("window.");
ok(popup.getItems().length > 0, "'window.' gave a list of suggestions");
await jsterm.execute("window.docfoobar = true");
info("Add a property on the window object");
await ContentTask.spawn(gBrowser.selectedBrowser, {}, () => {
content.wrappedJSObject.window.docfoobar = true;
});
// Test typing 'window.doc'.
await jstermComplete("window.doc");
// Test typing d (i.e. input is now 'window.d').
let onUpdated = jsterm.once("autocomplete-updated");
EventUtils.synthesizeKey("d");
await onUpdated;
ok(!getPopupLabels(popup).includes("docfoobar"),
"autocomplete cached results do not contain docfoobar. list has not been updated");
"autocomplete popup does not contain docfoobar. List has not been updated");
// Test typing o (i.e. input is now 'window.do').
jsterm.once("autocomplete-updated");
EventUtils.synthesizeKey("o");
await onUpdated;
ok(!getPopupLabels(popup).includes("docfoobar"),
"autocomplete popup does not contain docfoobar. List has not been updated");
// Test that backspace does not cause a request to the server
await jstermComplete("window.do");
onUpdated = jsterm.once("autocomplete-updated");
EventUtils.synthesizeKey("KEY_Backspace");
await onUpdated;
ok(!getPopupLabels(popup).includes("docfoobar"),
"autocomplete cached results do not contain docfoobar. list has not been updated");
await jsterm.execute("delete window.docfoobar");
await ContentTask.spawn(gBrowser.selectedBrowser, {}, () => {
delete content.wrappedJSObject.window.docfoobar;
});
// Test if 'window.getC' gives 'getComputedStyle'
await jstermComplete("window.");
@ -58,13 +78,18 @@ add_task(async function() {
await jstermComplete("dump(window.)", -1);
ok(popup.getItems().length > 0, "'dump(window.' gave a list of suggestions");
await jsterm.execute("window.docfoobar = true");
info("Add a property on the window object");
await ContentTask.spawn(gBrowser.selectedBrowser, {}, () => {
content.wrappedJSObject.window.docfoobar = true;
});
// Make sure 'dump(window.doc)' does not contain 'docfoobar'.
await jstermComplete("dump(window.doc)", -1);
// Make sure 'dump(window.d)' does not contain 'docfoobar'.
onUpdated = jsterm.once("autocomplete-updated");
EventUtils.synthesizeKey("d");
await onUpdated;
ok(!getPopupLabels(popup).includes("docfoobar"),
"autocomplete cached results do not contain docfoobar. list has not been updated");
});
}
function getPopupLabels(popup) {
return popup.getItems().map(item => item.label);

View File

@ -8,7 +8,16 @@
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
"test/mochitest/test-iframe-parent.html";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const { jsterm } = hud;
@ -34,4 +43,4 @@ add_task(async function() {
hud.jsterm.execute("window.location");
await onParentLocation;
ok(true, "root document's location is accessible");
});
}

View File

@ -23,13 +23,18 @@ const TEST_URI = `data:text/html;charset=utf-8,
<body>bug 585991 - autocomplete popup escape key usage test</body>`;
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const { jsterm } = await openNewTabAndConsole(TEST_URI);
info("web console opened");
const {
autocompletePopup: popup,
completeNode,
} = jsterm;
const { autocompletePopup: popup } = jsterm;
const onPopUpOpen = popup.once("popup-opened");
@ -50,5 +55,5 @@ add_task(async function() {
ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
is(jsterm.getInputValue(), "window.foo.", "completion was cancelled");
ok(!completeNode.value, "completeNode is empty");
});
ok(!getJsTermCompletionValue(jsterm), "completeNode is empty");
}

View File

@ -11,6 +11,14 @@
const TEST_URI = "data:text/html;charset=utf-8,test for bug 592442";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const { jsterm } = await openNewTabAndConsole(TEST_URI);
try {
@ -19,4 +27,4 @@ add_task(async function() {
} catch (ex) {
ok(false, "an error was thrown when an extraneous bracket was inserted");
}
});
}

View File

@ -11,12 +11,20 @@
const TEST_URI = "data:text/html;charset=utf8,<p>test JSTerm Helpers autocomplete";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const {jsterm} = await openNewTabAndConsole(TEST_URI);
await testInspectAutoCompletion(jsterm, "i", true);
await testInspectAutoCompletion(jsterm, "window.", false);
await testInspectAutoCompletion(jsterm, "dump(i", true);
await testInspectAutoCompletion(jsterm, "window.dump(i", true);
});
}
async function testInspectAutoCompletion(jsterm, inputValue, expectInspect) {
jsterm.setInputValue(inputValue);

View File

@ -8,6 +8,14 @@
"use strict";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole("about:config");
ok(hud, "we have a console");
ok(hud.iframeWindow, "we have the console UI window");
@ -18,5 +26,5 @@ add_task(async function() {
// Test typing 'docu'.
await jstermSetValueAndComplete(jsterm, "docu");
is(jsterm.completeNode.value, " ment", "'docu' completion");
});
checkJsTermCompletionValue(jsterm, " ment", "'docu' completion");
}

View File

@ -12,6 +12,14 @@ const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
"test/mochitest/test-autocomplete-in-stackframe.html";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
// Force the old debugger UI since it's directly used (see Bug 1301705)
await pushPref("devtools.debugger.new-debugger-frontend", false);
@ -93,7 +101,7 @@ add_task(async function() {
// Test if 'foo2Obj[0].' throws no errors.
await jstermComplete("foo2Obj[0].");
is(getPopupLabels(popup).length, 0, "no items for foo2Obj[0]");
});
}
function getPopupLabels(popup) {
return popup.getItems().map(item => item.label);

View File

@ -17,21 +17,23 @@ const TEST_URI = `data:text/html;charset=utf-8,
<body>bug 812618 - test completion inside text</body>`;
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const { jsterm } = await openNewTabAndConsole(TEST_URI);
info("web console opened");
const {
autocompletePopup: popup,
completeNode,
inputNode,
} = jsterm;
const { autocompletePopup: popup } = jsterm;
const onPopUpOpen = popup.once("popup-opened");
const dumpString = "dump(window.testBu)";
jsterm.setInputValue(dumpString);
inputNode.selectionStart = inputNode.selectionEnd = dumpString.indexOf(")");
EventUtils.sendString("g");
jstermSetValueAndComplete(jsterm, dumpString, -1);
await onPopUpOpen;
@ -40,7 +42,7 @@ add_task(async function() {
EventUtils.synthesizeKey("KEY_ArrowDown");
is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
ok(!completeNode.value, "completeNode.value is empty");
ok(!getJsTermCompletionValue(jsterm), "completeNode.value is empty");
const items = popup.getItems().map(e => e.label);
const expectedItems = ["testBugB", "testBugA"];
@ -56,7 +58,6 @@ add_task(async function() {
ok(!popup.isOpen, "popup is not open");
const expectedInput = "dump(window.testBugB)";
is(jsterm.getInputValue(), expectedInput, "completion was successful after VK_TAB");
is(inputNode.selectionStart, expectedInput.length - 1, "cursor location is correct");
is(inputNode.selectionStart, inputNode.selectionEnd, "cursor location (confirmed)");
ok(!completeNode.value, "completeNode is empty");
});
checkJsTermCursor(jsterm, expectedInput.length - 1, "cursor location is correct");
ok(!getJsTermCompletionValue(jsterm), "completeNode is empty");
}

View File

@ -11,12 +11,17 @@
const TEST_URI = "data:text/html;charset=utf-8,Test document.body autocompletion";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const { jsterm } = await openNewTabAndConsole(TEST_URI);
const {
autocompletePopup: popup,
completeNode,
} = jsterm;
const { autocompletePopup: popup } = jsterm;
ok(!popup.isOpen, "popup is not open");
const onPopupOpen = popup.once("popup-opened");
@ -29,7 +34,7 @@ add_task(async function() {
ok(popup.isOpen, "popup is open");
is(popup.itemCount, jsterm._autocompleteCache.length, "popup.itemCount is correct");
ok(jsterm._autocompleteCache.includes("addEventListener"),
"addEventListener is in the list of suggestions");
"addEventListener is in the list of suggestions");
ok(jsterm._autocompleteCache.includes("bgColor"),
"bgColor is in the list of suggestions");
ok(jsterm._autocompleteCache.includes("ATTRIBUTE_NODE"),
@ -53,5 +58,5 @@ add_task(async function() {
// > document.bo <-- input
// > -----------dy <-- autocomplete
const spaces = " ".repeat(inputStr.length + 1);
is(completeNode.value, spaces + "dy", "autocomplete shows document.body");
});
checkJsTermCompletionValue(jsterm, spaces + "dy", "autocomplete shows document.body");
}

View File

@ -25,13 +25,18 @@ const TEST_URI = `data:text/html;charset=utf-8,
<body>bug 585991 - autocomplete popup navigation and tab key usage test</body>`;
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const { jsterm } = await openNewTabAndConsole(TEST_URI);
info("web console opened");
const {
autocompletePopup: popup,
completeNode,
} = jsterm;
const { autocompletePopup: popup } = jsterm;
ok(!popup.isOpen, "popup is not open");
@ -63,19 +68,19 @@ add_task(async function() {
const prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "item3", "item3 is selected");
is(completeNode.value, prefix + "item3", "completeNode.value holds item3");
checkJsTermCompletionValue(jsterm, prefix + "item3", "completeNode.value holds item3");
EventUtils.synthesizeKey("KEY_ArrowDown");
is(popup.selectedIndex, 1, "index 1 is selected");
is(popup.selectedItem.label, "item2", "item2 is selected");
is(completeNode.value, prefix + "item2", "completeNode.value holds item2");
checkJsTermCompletionValue(jsterm, prefix + "item2", "completeNode.value holds item2");
EventUtils.synthesizeKey("KEY_ArrowUp");
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "item3", "item3 is selected");
is(completeNode.value, prefix + "item3", "completeNode.value holds item3");
checkJsTermCompletionValue(jsterm, prefix + "item3", "completeNode.value holds item3");
let currentSelectionIndex = popup.selectedIndex;
@ -104,5 +109,5 @@ add_task(async function() {
ok(!popup.isOpen, "popup is not open");
is(jsterm.getInputValue(), "window.foo.item3",
"completion was successful after KEY_Tab");
ok(!completeNode.value, "completeNode is empty");
});
ok(!getJsTermCompletionValue(jsterm), "completeNode is empty");
}

View File

@ -16,9 +16,17 @@ XPCOMUtils.defineLazyServiceGetter(
const stringToCopy = "foobazbarBug642615";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const {jsterm, ui} = await openNewTabAndConsole(TEST_URI);
ui.clearOutput();
ok(!jsterm.completeNode.value, "no completeNode.value");
ok(!getJsTermCompletionValue(jsterm), "no completeNode.value");
jsterm.setInputValue("doc");
@ -27,7 +35,7 @@ add_task(async function() {
EventUtils.sendString("u");
await onAutocompleteUpdated;
const completionValue = jsterm.completeNode.value;
const completionValue = getJsTermCompletionValue(jsterm);
info(`Copy "${stringToCopy}" in clipboard`);
await waitForClipboardPromise(() =>
@ -36,20 +44,21 @@ add_task(async function() {
jsterm.setInputValue("docu");
info("wait for completion update after clipboard paste");
onAutocompleteUpdated = jsterm.once("autocomplete-updated");
goDoCommand("cmd_paste");
EventUtils.synthesizeKey("v", {accelKey: true});
await onAutocompleteUpdated;
ok(!jsterm.completeNode.value, "no completion value after paste");
ok(!getJsTermCompletionValue(jsterm), "no completion value after paste");
info("wait for completion update after undo");
onAutocompleteUpdated = jsterm.once("autocomplete-updated");
goDoCommand("cmd_undo");
EventUtils.synthesizeKey("z", {accelKey: true});
await onAutocompleteUpdated;
is(jsterm.completeNode.value, completionValue, "same completeNode.value after undo");
checkJsTermCompletionValue(jsterm, completionValue,
"same completeNode.value after undo");
info("wait for completion update after clipboard paste (ctrl-v)");
onAutocompleteUpdated = jsterm.once("autocomplete-updated");
@ -57,5 +66,5 @@ add_task(async function() {
EventUtils.synthesizeKey("v", {accelKey: true});
await onAutocompleteUpdated;
ok(!jsterm.completeNode.value, "no completion value after paste (ctrl-v)");
});
ok(!getJsTermCompletionValue(jsterm), "no completion value after paste (ctrl-v)");
}

View File

@ -25,11 +25,16 @@ const TEST_URI = `data:text/html;charset=utf-8,
<body>bug 585991 - test pressing return with open popup</body>`;
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const { jsterm } = await openNewTabAndConsole(TEST_URI);
const {
autocompletePopup: popup,
completeNode,
} = jsterm;
const { autocompletePopup: popup } = jsterm;
const onPopUpOpen = popup.once("popup-opened");
@ -57,7 +62,7 @@ add_task(async function() {
is(popup.selectedIndex, 0, "index 0 is selected");
is(popup.selectedItem.label, "item3", "item3 is selected");
const prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
is(completeNode.value, prefix + "item3", "completeNode.value holds item3");
checkJsTermCompletionValue(jsterm, prefix + "item3", "completeNode.value holds item3");
info("press Return to accept suggestion. wait for popup to hide");
const onPopupClose = popup.once("popup-closed");
@ -68,5 +73,5 @@ add_task(async function() {
ok(!popup.isOpen, "popup is not open after KEY_Enter");
is(jsterm.getInputValue(), "window.foobar.item3",
"completion was successful after KEY_Enter");
ok(!completeNode.value, "completeNode is empty");
});
ok(!getJsTermCompletionValue(jsterm), "completeNode is empty");
}

View File

@ -21,15 +21,20 @@ const {
} = require("devtools/client/webconsole/selectors/history");
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const {
jsterm,
ui,
} = await openNewTabAndConsole(TEST_URI);
const {
autocompletePopup: popup,
completeNode,
} = jsterm;
const { autocompletePopup: popup } = jsterm;
const onPopUpOpen = popup.once("popup-opened");
@ -50,10 +55,10 @@ add_task(async function() {
ok(!popup.isOpen, "popup is not open after KEY_Enter");
is(jsterm.getInputValue(), "", "inputNode is empty after KEY_Enter");
is(completeNode.value, "", "completeNode is empty");
ok(!getJsTermCompletionValue(jsterm), "completeNode is empty");
const state = ui.consoleOutput.getStore().getState();
const entries = getHistoryEntries(state);
is(entries[entries.length - 1], "window.testBug",
"jsterm history is correct");
});
}

View File

@ -10,64 +10,71 @@
const TEST_URI = "data:text/html;charset=utf8,<p>test code completion";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const {jsterm, ui} = await openNewTabAndConsole(TEST_URI);
const input = jsterm.inputNode;
// Test typing 'docu'.
await jstermSetValueAndComplete(jsterm, "docu");
is(input.value, "docu", "'docu' completion (input.value)");
is(jsterm.completeNode.value, " ment", "'docu' completion (completeNode)");
is(jsterm.getInputValue(), "docu", "'docu' completion (input.value)");
checkJsTermCompletionValue(jsterm, " ment", "'docu' completion (completeNode)");
// Test typing 'docu' and press tab.
await jstermSetValueAndComplete(jsterm, "docu", undefined, jsterm.COMPLETE_FORWARD);
is(input.value, "document", "'docu' tab completion");
is(input.selectionStart, 8, "start selection is alright");
is(input.selectionEnd, 8, "end selection is alright");
is(jsterm.completeNode.value.replace(/ /g, ""), "", "'docu' completed");
is(jsterm.getInputValue(), "document", "'docu' tab completion");
checkJsTermCursor(jsterm, "document".length, "cursor is at the end of 'document'");
is(getJsTermCompletionValue(jsterm).replace(/ /g, ""), "", "'docu' completed");
// Test typing 'window.Ob' and press tab. Just 'window.O' is
// ambiguous: could be window.Object, window.Option, etc.
await jstermSetValueAndComplete(jsterm, "window.Ob", undefined,
jsterm.COMPLETE_FORWARD);
is(input.value, "window.Object", "'window.Ob' tab completion");
is(jsterm.getInputValue(), "window.Object", "'window.Ob' tab completion");
// Test typing 'document.getElem'.
await jstermSetValueAndComplete(
jsterm, "document.getElem", undefined, jsterm.COMPLETE_FORWARD);
is(input.value, "document.getElem", "'document.getElem' completion");
is(jsterm.completeNode.value, " entsByTagNameNS",
is(jsterm.getInputValue(), "document.getElem", "'document.getElem' completion");
checkJsTermCompletionValue(jsterm, " entsByTagNameNS",
"'document.getElem' completion");
// Test pressing tab another time.
await jsterm.complete(jsterm.COMPLETE_FORWARD);
is(input.value, "document.getElem", "'document.getElem' completion");
is(jsterm.completeNode.value, " entsByTagName",
is(jsterm.getInputValue(), "document.getElem", "'document.getElem' completion");
checkJsTermCompletionValue(jsterm, " entsByTagName",
"'document.getElem' another tab completion");
// Test pressing shift_tab.
await jstermComplete(jsterm, jsterm.COMPLETE_BACKWARD);
is(input.value, "document.getElem", "'document.getElem' untab completion");
is(jsterm.completeNode.value, " entsByTagNameNS",
is(jsterm.getInputValue(), "document.getElem", "'document.getElem' untab completion");
checkJsTermCompletionValue(jsterm, " entsByTagNameNS",
"'document.getElem' completion");
ui.clearOutput();
await jstermSetValueAndComplete(jsterm, "docu");
is(jsterm.completeNode.value, " ment", "'docu' completion");
checkJsTermCompletionValue(jsterm, " ment", "'docu' completion");
await jsterm.execute();
is(jsterm.completeNode.value, "", "clear completion on execute()");
checkJsTermCompletionValue(jsterm, "", "clear completion on execute()");
// Test multi-line completion works
await jstermSetValueAndComplete(jsterm, "console.log('one');\nconsol");
is(jsterm.completeNode.value, " \n e",
checkJsTermCompletionValue(jsterm, " \n e",
"multi-line completion");
// Test non-object autocompletion.
await jstermSetValueAndComplete(jsterm, "Object.name.sl");
is(jsterm.completeNode.value, " ice", "non-object completion");
checkJsTermCompletionValue(jsterm, " ice", "non-object completion");
// Test string literal autocompletion.
await jstermSetValueAndComplete(jsterm, "'Asimov'.sl");
is(jsterm.completeNode.value, " ice", "string literal completion");
});
checkJsTermCompletionValue(jsterm, " ice", "string literal completion");
}

View File

@ -27,6 +27,14 @@ const TEST_URI = `data:text/html;charset=utf-8,
</body>`;
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const {jsterm} = await openNewTabAndConsole(TEST_URI);
const random = Math.random();
const string = "Text: " + random;
@ -42,7 +50,7 @@ add_task(async function() {
}
);
await testCopy(jsterm, `$("#${id}")`, outerHTML);
});
}
function testCopy(jsterm, stringToCopy, expectedResult) {
return waitForClipboardPromise(() => {

View File

@ -9,27 +9,64 @@
const TEST_URI = "data:text/html;charset=utf-8,Test console select all";
add_task(async function testCtrlA() {
const hud = await openNewTabAndConsole(TEST_URI);
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const {jsterm} = await openNewTabAndConsole(TEST_URI);
const jsterm = hud.jsterm;
jsterm.setInputValue("Ignore These Four Words");
const inputNode = jsterm.inputNode;
// Test select all with (cmd|control) + a.
EventUtils.synthesizeKey("a", { accelKey: true });
const inputLength = inputNode.selectionEnd - inputNode.selectionStart;
const inputLength = getSelectionTextLength(jsterm);
is(inputLength, jsterm.getInputValue().length, "Select all of input");
// (cmd|control) + e cannot be disabled on Linux so skip this section on that
// OS.
// (cmd|control) + e cannot be disabled on Linux so skip this section on that OS.
if (Services.appinfo.OS !== "Linux") {
// Test do nothing on Control + E.
jsterm.setInputValue("Ignore These Four Words");
inputNode.selectionStart = 0;
setCursorAtStart(jsterm);
EventUtils.synthesizeKey("e", { accelKey: true });
is(inputNode.selectionStart, 0,
"control|cmd + e does not move to end of input");
checkSelectionStart(jsterm, 0, "control|cmd + e does not move to end of input");
}
});
}
function getSelectionTextLength(jsterm) {
if (jsterm.inputNode) {
return jsterm.inputNode.selectionEnd - jsterm.inputNode.selectionStart;
}
if (jsterm.editor) {
return jsterm.editor.getSelection().length;
}
return null;
}
function setCursorAtStart(jsterm) {
if (jsterm.inputNode) {
jsterm.inputNode.selectionStart = 0;
}
if (jsterm.editor) {
jsterm.editor.setCursor({line: 0, ch: 0});
}
}
function checkSelectionStart(jsterm, expectedCursorIndex, assertionInfo) {
if (jsterm.inputNode) {
const { selectionStart } = jsterm.inputNode;
is(selectionStart, expectedCursorIndex, assertionInfo);
} else {
const [ selection ] = jsterm.editor.codeMirror.listSelections();
const { head} = selection;
is(head.ch, expectedCursorIndex, assertionInfo);
}
}

View File

@ -15,214 +15,267 @@ const TEST_URI = "data:text/html;charset=utf-8,Web Console test for " +
"bug 804845 and bug 619598";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const {jsterm} = await openNewTabAndConsole(TEST_URI);
ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
is(jsterm.inputNode.selectionStart, 0);
is(jsterm.inputNode.selectionEnd, 0);
checkJsTermCursor(jsterm, 0, "Cursor is at the start of the input");
testSingleLineInputNavNoHistory(jsterm);
testMultiLineInputNavNoHistory(jsterm);
await testNavWithHistory(jsterm);
});
}
function testSingleLineInputNavNoHistory(jsterm) {
const inputNode = jsterm.inputNode;
const checkInput = (expected, assertionInfo) =>
checkJsTermValueAndCursor(jsterm, expected, assertionInfo);
// Single char input
EventUtils.sendString("1");
is(inputNode.selectionStart, 1, "caret location after single char input");
checkInput("1|", "caret location after single char input");
// nav to start/end with ctrl-a and ctrl-e;
synthesizeLineStartKey();
is(inputNode.selectionStart, 0,
"caret location after single char input and ctrl-a");
checkInput("|1", "caret location after single char input and ctrl-a");
synthesizeLineEndKey();
is(inputNode.selectionStart, 1,
"caret location after single char input and ctrl-e");
checkInput("1|", "caret location after single char input and ctrl-e");
// Second char input
EventUtils.sendString("2");
checkInput("12|", "caret location after second char input");
// nav to start/end with up/down keys; verify behaviour using ctrl-p/ctrl-n
EventUtils.synthesizeKey("KEY_ArrowUp");
is(inputNode.selectionStart, 0,
"caret location after two char input and KEY_ArrowUp");
checkInput("|12", "caret location after two char input and KEY_ArrowUp");
EventUtils.synthesizeKey("KEY_ArrowDown");
is(inputNode.selectionStart, 2,
"caret location after two char input and KEY_ArrowDown");
checkInput("12|", "caret location after two char input and KEY_ArrowDown");
synthesizeLineStartKey();
is(inputNode.selectionStart, 0,
"move caret to beginning of 2 char input with ctrl-a");
checkInput("|12", "move caret to beginning of 2 char input with ctrl-a");
synthesizeLineStartKey();
is(inputNode.selectionStart, 0,
"no change of caret location on repeat ctrl-a");
synthesizeLineUpKey();
is(inputNode.selectionStart, 0,
"no change of caret location on ctrl-p from beginning of line");
synthesizeLineEndKey();
is(inputNode.selectionStart, 2,
"move caret to end of 2 char input with ctrl-e");
synthesizeLineEndKey();
is(inputNode.selectionStart, 2,
"no change of caret location on repeat ctrl-e");
synthesizeLineDownKey();
is(inputNode.selectionStart, 2,
"no change of caret location on ctrl-n from end of line");
checkInput("|12", "no change of caret location on repeat ctrl-a");
synthesizeLineUpKey();
is(inputNode.selectionStart, 0, "ctrl-p moves to start of line");
checkInput("|12", "no change of caret location on ctrl-p from beginning of line");
synthesizeLineEndKey();
checkInput("12|", "move caret to end of 2 char input with ctrl-e");
synthesizeLineEndKey();
checkInput("12|", "no change of caret location on repeat ctrl-e");
synthesizeLineDownKey();
is(inputNode.selectionStart, 2, "ctrl-n moves to end of line");
checkInput("12|", "no change of caret location on ctrl-n from end of line");
synthesizeLineUpKey();
checkInput("|12", "ctrl-p moves to start of line");
synthesizeLineDownKey();
checkInput("12|", "ctrl-n moves to end of line");
}
function testMultiLineInputNavNoHistory(jsterm) {
const inputNode = jsterm.inputNode;
const checkInput = (expected, assertionInfo) =>
checkJsTermValueAndCursor(jsterm, expected, assertionInfo);
const lineValues = ["one", "2", "something longer", "", "", "three!"];
jsterm.setInputValue("");
// simulate shift-return
for (let i = 0; i < lineValues.length; i++) {
jsterm.setInputValue(jsterm.getInputValue() + lineValues[i]);
for (const lineValue of lineValues) {
jsterm.setInputValue(jsterm.getInputValue() + lineValue);
EventUtils.synthesizeKey("KEY_Enter", {shiftKey: true});
}
const inputValue = jsterm.getInputValue();
is(inputNode.selectionStart, inputNode.selectionEnd);
is(inputNode.selectionStart, inputValue.length,
"caret at end of multiline input");
// possibility newline is represented by one ('\r', '\n') or two
// ('\r\n') chars
const newlineString = inputValue.match(/(\r\n?|\n\r?)$/)[0];
checkInput(
`one
2
something longer
three!
|`, "caret at end of multiline input");
// Ok, test navigating within the multi-line string!
EventUtils.synthesizeKey("KEY_ArrowUp");
let expectedStringAfterCarat = lineValues[5] + newlineString;
is(jsterm.getInputValue().slice(inputNode.selectionStart), expectedStringAfterCarat,
"up arrow from end of multiline");
checkInput(
`one
2
something longer
|three!
`, "up arrow from end of multiline");
EventUtils.synthesizeKey("KEY_ArrowDown");
is(jsterm.getInputValue().slice(inputNode.selectionStart), "",
"down arrow from within multiline");
checkInput(
`one
2
something longer
three!
|`, "down arrow from within multiline");
// navigate up through input lines
synthesizeLineUpKey();
is(jsterm.getInputValue().slice(inputNode.selectionStart), expectedStringAfterCarat,
"ctrl-p from end of multiline");
checkInput(
`one
2
something longer
for (let i = 4; i >= 0; i--) {
|three!
`, "ctrl-p from end of multiline");
for (let i = 0; i < 5; i++) {
synthesizeLineUpKey();
expectedStringAfterCarat = lineValues[i] + newlineString +
expectedStringAfterCarat;
is(jsterm.getInputValue().slice(inputNode.selectionStart),
expectedStringAfterCarat, "ctrl-p from within line " + i +
" of multiline input");
}
checkInput(
`|one
2
something longer
three!
`, "reached start of input");
synthesizeLineUpKey();
is(inputNode.selectionStart, 0, "reached start of input");
is(jsterm.getInputValue(), inputValue,
"no change to multiline input on ctrl-p from beginning of multiline");
checkInput(
`|one
2
something longer
three!
`, "no change to multiline input on ctrl-p from beginning of multiline");
// navigate to end of first line
synthesizeLineEndKey();
let caretPos = inputNode.selectionStart;
let expectedStringBeforeCarat = lineValues[0];
is(jsterm.getInputValue().slice(0, caretPos), expectedStringBeforeCarat,
"ctrl-e into multiline input");
checkInput(
`one|
2
something longer
three!
`, "ctrl-e into multiline input");
synthesizeLineEndKey();
is(inputNode.selectionStart, caretPos,
"repeat ctrl-e doesn't change caret position in multiline input");
checkInput(
`one|
2
something longer
// navigate down one line; ctrl-a to the beginning; ctrl-e to end
for (let i = 1; i < lineValues.length; i++) {
synthesizeLineDownKey();
synthesizeLineStartKey();
caretPos = inputNode.selectionStart;
expectedStringBeforeCarat += newlineString;
is(jsterm.getInputValue().slice(0, caretPos), expectedStringBeforeCarat,
"ctrl-a to beginning of line " + (i + 1) + " in multiline input");
synthesizeLineEndKey();
caretPos = inputNode.selectionStart;
expectedStringBeforeCarat += lineValues[i];
is(jsterm.getInputValue().slice(0, caretPos), expectedStringBeforeCarat,
"ctrl-e to end of line " + (i + 1) + "in multiline input");
}
three!
`, "repeat ctrl-e doesn't change caret position in multiline input");
synthesizeLineDownKey();
synthesizeLineStartKey();
checkInput(
`one
|2
something longer
three!
`);
synthesizeLineEndKey();
synthesizeLineDownKey();
synthesizeLineStartKey();
checkInput(
`one
2
|something longer
three!
`);
}
async function testNavWithHistory(jsterm) {
const inputNode = jsterm.inputNode;
const checkInput = (expected, assertionInfo) =>
checkJsTermValueAndCursor(jsterm, expected, assertionInfo);
// NOTE: Tests does NOT currently define behaviour for ctrl-p/ctrl-n with
// caret placed _within_ single line input
const values = [
'"single line input"',
'"a longer single-line input to check caret repositioning"',
'"multi-line"\n"input"\n"here!"',
"single line input",
"a longer single-line input to check caret repositioning",
"multi-line\ninput\nhere",
];
// submit to history
for (let i = 0; i < values.length; i++) {
jsterm.setInputValue(values[i]);
for (const value of values) {
jsterm.setInputValue(value);
await jsterm.execute();
}
is(inputNode.selectionStart, 0, "caret location at start of empty line");
checkInput("|", "caret location at start of empty line");
synthesizeLineUpKey();
is(inputNode.selectionStart, values[values.length - 1].length,
"caret location correct at end of last history input");
checkInput("multi-line\ninput\nhere|", "caret location at end of last history input");
// Navigate backwards history with ctrl-p
for (let i = values.length - 1; i > 0; i--) {
const match = values[i].match(/(\n)/g);
if (match) {
// multi-line inputs won't update from history unless caret at beginning
synthesizeLineStartKey();
for (let j = 0; j < match.length; j++) {
synthesizeLineUpKey();
}
synthesizeLineUpKey();
} else {
// single-line inputs will update from history from end of line
synthesizeLineUpKey();
}
is(jsterm.getInputValue(), values[i - 1],
"ctrl-p updates inputNode from backwards history values[" + i - 1 + "]");
}
synthesizeLineStartKey();
checkInput("multi-line\ninput\n|here",
"caret location at beginning of last line of last history input");
let inputValue = jsterm.getInputValue();
synthesizeLineUpKey();
is(inputNode.selectionStart, 0,
"ctrl-p at beginning of history moves caret location to beginning " +
"of line");
is(jsterm.getInputValue(), inputValue,
"no change to input value on ctrl-p from beginning of line");
checkInput("multi-line\n|input\nhere",
"caret location at beginning of second line of last history input");
synthesizeLineUpKey();
checkInput("|multi-line\ninput\nhere",
"caret location at beginning of first line of last history input");
synthesizeLineUpKey();
checkInput("a longer single-line input to check caret repositioning|",
"caret location at the end of second history input");
synthesizeLineUpKey();
checkInput("single line input|", "caret location at the end of first history input");
synthesizeLineUpKey();
checkInput("|single line input",
"ctrl-p at beginning of history moves caret location to beginning of line");
// Navigate forwards history with ctrl-n
for (let i = 1; i < values.length; i++) {
synthesizeLineDownKey();
is(jsterm.getInputValue(), values[i],
"ctrl-n updates inputNode from forwards history values[" + i + "]");
is(inputNode.selectionStart, values[i].length,
"caret location correct at end of history input for values[" + i + "]");
}
synthesizeLineDownKey();
ok(!jsterm.getInputValue(), "ctrl-n at end of history updates to empty input");
checkInput("a longer single-line input to check caret repositioning|",
"caret location at the end of second history input");
synthesizeLineDownKey();
checkInput("multi-line\ninput\nhere|", "caret location at end of last history input");
synthesizeLineDownKey();
checkInput("|", "ctrl-n at end of history updates to empty input");
// Simulate editing multi-line
inputValue = "one\nlinebreak";
const inputValue = "one\nlinebreak";
jsterm.setInputValue(inputValue);
checkInput("one\nlinebreak|");
// Attempt nav within input
synthesizeLineUpKey();
is(jsterm.getInputValue(), inputValue,
"ctrl-p from end of multi-line does not trigger history");
checkInput("one|\nlinebreak", "ctrl-p from end of multi-line does not trigger history");
synthesizeLineStartKey();
checkInput("|one\nlinebreak");
synthesizeLineUpKey();
is(jsterm.getInputValue(), values[values.length - 1],
"ctrl-p from start of multi-line triggers history");
checkInput("multi-line\ninput\nhere|",
"ctrl-p from start of multi-line triggers history");
}
function synthesizeLineStartKey() {

View File

@ -6,6 +6,14 @@
const TEST_URI = "data:text/html,Test evaluating document";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const {jsterm} = hud;
@ -14,4 +22,4 @@ add_task(async function() {
jsterm.execute("document");
const {node} = await onMessage;
is(node.textContent.includes("xray"), false, "document - no XrayWrapper");
});
}

View File

@ -12,10 +12,18 @@ const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
"test/mochitest/test-jsterm-dollar.html";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
await test$(hud);
await test$$(hud);
});
}
async function test$(hud) {
hud.ui.clearOutput();

View File

@ -6,6 +6,14 @@
const TEST_URI = "data:text/html,Test error documentation";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const {jsterm} = hud;
@ -30,4 +38,4 @@ add_task(async function() {
ok(learnMoreLink, `There is a [Learn More] link for "${errorMessageName}" error`);
is(learnMoreLink.title, title, `The link has the expected "${title}" title`);
}
});
}

View File

@ -9,6 +9,14 @@
const TEST_URI = "data:text/html,Test error documentation";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const {jsterm} = hud;
@ -18,4 +26,4 @@ add_task(async function() {
jsterm.execute("new Request('',{redirect:'foo'})");
await onErrorMessage;
ok(true, "Error message displayed as expected, without crashing the console.");
});
}

View File

@ -6,6 +6,14 @@
const TEST_URI = "data:text/html,Test <code>clear()</code> jsterm helper";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const onMessage = waitForMessage(hud, "message");
@ -19,4 +27,4 @@ add_task(async function() {
hud.jsterm.execute("clear()");
await onCleared;
ok(true, "Console was cleared");
});
}

View File

@ -16,6 +16,14 @@ const TEST_URI = `data:text/html,
`;
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const jsterm = hud.jsterm;
@ -38,4 +46,4 @@ add_task(async function() {
jsterm.execute("$('div')");
message = await onMessage;
ok(message, "`$('div')` does return null");
});
}

View File

@ -16,6 +16,14 @@ const TEST_URI = `data:text/html,
`;
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const jsterm = hud.jsterm;
@ -38,4 +46,4 @@ add_task(async function() {
jsterm.execute("$$('div')");
message = await onMessage;
ok(message, "`$$('div')` returns an empty array");
});
}

View File

@ -16,6 +16,14 @@ const TEST_URI = `data:text/html,
`;
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const jsterm = hud.jsterm;
@ -28,4 +36,4 @@ add_task(async function() {
jsterm.execute("$x('.//li', document.body)[0]");
message = await onMessage;
ok(message, "`$x()` result can be used right away");
});
}

View File

@ -7,6 +7,14 @@ const TEST_URI = "data:text/html,Test <code>help()</code> jsterm helper";
const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const jsterm = hud.jsterm;
@ -28,4 +36,4 @@ add_task(async function() {
"There is no results shown for the help commands");
is(openedLinks, 3, "correct number of pages opened by the help calls");
hud.openLink = oldOpenLink;
});
}

View File

@ -7,6 +7,14 @@ const TEST_URI =
"data:text/html,Test <code>keys()</code> & <code>values()</code> jsterm helper";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const jsterm = hud.jsterm;
@ -24,4 +32,4 @@ add_task(async function() {
jsterm.execute("keys(window)");
message = await onMessage;
ok(message, "`keys(window)` worked");
});
}

View File

@ -6,6 +6,14 @@
const TEST_URI = "data:text/html,Test <code>pprint()</code> jsterm helper";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const {jsterm} = hud;
@ -31,4 +39,4 @@ add_task(async function() {
jsterm.execute("pprint(function() { var someCanaryValue = 42; })");
message = await onMessage;
ok(message, "`pprint(function)` shows function source");
});
}

View File

@ -23,6 +23,14 @@
"use strict";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
let browserConsole, webConsole, objInspector;
// We don't use `pushPref()` because we need to revert the same pref later
@ -52,8 +60,11 @@ add_task(async function() {
objInspector = await getObjectInspector(webConsole);
testJSTermIsVisible(webConsole);
await testObjectInspectorPropertiesAreSet(objInspector);
info("Close webconsole and browser console");
await closeConsole(browserTab);
});
await HUDService.toggleBrowserConsole();
}
/**
* Returns either the Variables View or Object Inspector depending on which is

View File

@ -30,8 +30,11 @@ async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const { jsterm } = hud;
const checkInput = (expected, assertionInfo) =>
checkJsTermValueAndCursor(jsterm, expected, assertionInfo);
jsterm.focus();
ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
checkInput("|", "input is empty");
info("Execute each test value in the console");
for (const value of TEST_VALUES) {
@ -39,94 +42,67 @@ async function performTests() {
await jsterm.execute();
}
const values = TEST_VALUES;
EventUtils.synthesizeKey("KEY_ArrowUp");
is(jsterm.getInputValue(), values[4], "VK_UP: jsterm.getInputValue() #4 is correct");
is(getCaretPosition(jsterm), values[4].length, "caret location is correct");
checkInput("document.location|", "↑: input #4 is correct");
ok(inputHasNoSelection(jsterm));
EventUtils.synthesizeKey("KEY_ArrowUp");
is(jsterm.getInputValue(), values[3], "VK_UP: jsterm.getInputValue() #3 is correct");
is(getCaretPosition(jsterm), values[3].length, "caret location is correct");
checkInput("document;\nwindow;\ndocument.body|", "↑: input #3 is correct");
ok(inputHasNoSelection(jsterm));
info("Select text and ensure hitting arrow up twice won't navigate the history");
setCursorAtPosition(jsterm, values[3].length - 2);
info("Move cursor and ensure hitting arrow up twice won't navigate the history");
EventUtils.synthesizeKey("KEY_ArrowLeft");
EventUtils.synthesizeKey("KEY_ArrowLeft");
checkInput("document;\nwindow;\ndocument.bo|dy");
EventUtils.synthesizeKey("KEY_ArrowUp");
EventUtils.synthesizeKey("KEY_ArrowUp");
is(jsterm.getInputValue(), values[3],
"VK_UP two times: jsterm.getInputValue() #3 is correct");
is(getCaretPosition(jsterm), jsterm.getInputValue().indexOf("\n"),
"caret location is correct");
checkInput("document;|\nwindow;\ndocument.body", "↑↑: input #3 is correct");
ok(inputHasNoSelection(jsterm));
EventUtils.synthesizeKey("KEY_ArrowUp");
is(jsterm.getInputValue(), values[3],
"VK_UP again: jsterm.getInputValue() #3 is correct");
is(getCaretPosition(jsterm), 0, "caret location is correct");
checkInput("|document;\nwindow;\ndocument.body", "↑ again: input #3 is correct");
ok(inputHasNoSelection(jsterm));
EventUtils.synthesizeKey("KEY_ArrowUp");
is(jsterm.getInputValue(), values[2], "VK_UP: jsterm.getInputValue() #2 is correct");
checkInput("document.body|", "↑: input #2 is correct");
EventUtils.synthesizeKey("KEY_ArrowUp");
is(jsterm.getInputValue(), values[1], "VK_UP: jsterm.getInputValue() #1 is correct");
checkInput("window|", "↑: input #1 is correct");
EventUtils.synthesizeKey("KEY_ArrowUp");
is(jsterm.getInputValue(), values[0], "VK_UP: jsterm.getInputValue() #0 is correct");
is(getCaretPosition(jsterm), values[0].length, "caret location is correct");
checkInput("document|", "↑: input #0 is correct");
ok(inputHasNoSelection(jsterm));
EventUtils.synthesizeKey("KEY_ArrowDown");
is(jsterm.getInputValue(), values[1], "VK_DOWN: jsterm.getInputValue() #1 is correct");
is(getCaretPosition(jsterm), values[1].length, "caret location is correct");
checkInput("window|", "↓: input #1 is correct");
ok(inputHasNoSelection(jsterm));
EventUtils.synthesizeKey("KEY_ArrowDown");
is(jsterm.getInputValue(), values[2], "VK_DOWN: jsterm.getInputValue() #2 is correct");
checkInput("document.body|", "↓: input #2 is correct");
EventUtils.synthesizeKey("KEY_ArrowDown");
is(jsterm.getInputValue(), values[3], "VK_DOWN: jsterm.getInputValue() #3 is correct");
is(getCaretPosition(jsterm), values[3].length, "caret location is correct");
checkInput("document;\nwindow;\ndocument.body|", "↓: input #3 is correct");
ok(inputHasNoSelection(jsterm));
setCursorAtPosition(jsterm, 2);
checkInput("do|cument;\nwindow;\ndocument.body");
EventUtils.synthesizeKey("KEY_ArrowDown");
EventUtils.synthesizeKey("KEY_ArrowDown");
is(jsterm.getInputValue(), values[3],
"VK_DOWN two times: jsterm.getInputValue() #3 is correct");
ok(getCaretPosition(jsterm) > jsterm.getInputValue().lastIndexOf("\n")
&& inputHasNoSelection(jsterm),
"caret location is correct");
EventUtils.synthesizeKey("KEY_ArrowDown");
is(jsterm.getInputValue(), values[3],
"VK_DOWN again: jsterm.getInputValue() #3 is correct");
is(getCaretPosition(jsterm), values[3].length, "caret location is correct");
checkInput("document;\nwindow;\ndo|cument.body", "↓↓: input #3 is correct");
ok(inputHasNoSelection(jsterm));
EventUtils.synthesizeKey("KEY_ArrowDown");
is(jsterm.getInputValue(), values[4], "VK_DOWN: jsterm.getInputValue() #4 is correct");
checkInput("document;\nwindow;\ndocument.body|", "↓ again: input #3 is correct");
ok(inputHasNoSelection(jsterm));
EventUtils.synthesizeKey("KEY_ArrowDown");
checkInput("document.location|", "↓: input #4 is correct");
ok(!jsterm.getInputValue(), "VK_DOWN: jsterm.getInputValue() is empty");
EventUtils.synthesizeKey("KEY_ArrowDown");
checkInput("|", "↓: input is empty");
}
function setCursorAtPosition(jsterm, pos) {
@ -151,16 +127,6 @@ function setCursorAtPosition(jsterm, pos) {
return inputNode.setSelectionRange(pos, pos);
}
function getCaretPosition(jsterm) {
const {inputNode, editor} = jsterm;
if (editor) {
return editor.getDoc().getRange({line: 0, ch: 0}, editor.getCursor()).length;
}
return inputNode.selectionStart;
}
function inputHasNoSelection(jsterm) {
if (jsterm.editor) {
return !jsterm.editor.getDoc().getSelection();

View File

@ -10,29 +10,24 @@
const TEST_URI = "data:text/html;charset=utf-8,Test for jsterm multine input";
add_task(async function() {
const hud = await openNewTabAndConsole(TEST_URI);
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
const input = hud.jsterm.inputNode;
async function performTests() {
const {jsterm, ui} = await openNewTabAndConsole(TEST_URI);
const inputContainer = ui.window.document.querySelector(".jsterm-input-container");
info("Focus the jsterm input");
input.focus();
const ordinaryHeight = input.clientHeight;
const ordinaryHeight = inputContainer.clientHeight;
// Set a multiline value
input.value = "hello\nworld\n";
// Set the caret at the end of input
const length = input.value.length;
input.selectionEnd = length;
input.selectionStart = length;
info("Type 'd' in jsterm to trigger height change for the input");
EventUtils.sendString("d");
ok(input.clientHeight > ordinaryHeight, "the input expanded");
jsterm.setInputValue("hello\nworld\n");
ok(inputContainer.clientHeight > ordinaryHeight, "the input expanded");
info("Erase the value and test if the inputNode shrinks again");
input.value = "";
EventUtils.sendString("d");
is(input.clientHeight, ordinaryHeight, "the input's height is normal again");
});
jsterm.setInputValue("");
is(inputContainer.clientHeight, ordinaryHeight, "the input's height is normal again");
}

View File

@ -10,6 +10,14 @@
const TEST_URI = "data:text/html;charset=utf8,<p>test inspect() command";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const toolbox = await openNewTabAndToolbox(TEST_URI, "webconsole");
const hud = toolbox.getCurrentPanel().hud;
@ -55,7 +63,7 @@ add_task(async function() {
const inspectPrimitiveNode = await waitFor(() =>
findInspectResultMessage(hud.ui.outputNode, 2));
is(inspectPrimitiveNode.textContent, 1, "The primitive is displayed as expected");
});
}
function findInspectResultMessage(node, index) {
return node.querySelectorAll(".message.result")[index];

View File

@ -7,6 +7,14 @@
const TEST_URI = "data:text/html,Test <code>instanceof</code> evaluation";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const {jsterm} = hud;
@ -24,4 +32,4 @@ add_task(async function() {
jsterm.execute("({}) instanceof Array");
message = await onMessage;
ok(message, "`instanceof Array` has expected result");
});
}

View File

@ -76,6 +76,14 @@ const DATA = [
];
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
// Let's reset the counts.
Services.telemetry.clearEvents();
@ -83,37 +91,30 @@ add_task(async function() {
const snapshot = Services.telemetry.snapshotEvents(OPTOUT, true);
ok(!snapshot.parent, "No events have been logged for the main process");
const hud = await openNewTabAndConsole(TEST_URI);
const { inputNode } = hud.jsterm;
const {jsterm} = await openNewTabAndConsole(TEST_URI);
for (const {input, shiftKey} of SHOULD_ENTER_MULTILINE) {
hud.jsterm.setInputValue(input);
jsterm.setInputValue(input);
EventUtils.synthesizeKey("VK_RETURN", { shiftKey });
const inputValue = hud.jsterm.getInputValue();
is(inputNode.selectionStart, inputNode.selectionEnd, "selection is collapsed");
is(inputNode.selectionStart, inputValue.length, "caret at end of multiline input");
const inputWithNewline = input + "\n";
is(inputValue, inputWithNewline, "Input value is correct");
// We need to remove the spaces at the end of the input since code mirror do some
// automatic indent in some case.
const newValue = jsterm.getInputValue().replace(/ +$/g, "");
is(newValue, input + "\n", "A new line was added");
}
for (const {input, shiftKey} of SHOULD_EXECUTE) {
hud.jsterm.setInputValue(input);
jsterm.setInputValue(input);
EventUtils.synthesizeKey("VK_RETURN", { shiftKey });
await waitFor(() => !hud.jsterm.getInputValue());
const inputValue = hud.jsterm.getInputValue();
is(inputNode.selectionStart, 0, "selection starts/ends at 0");
is(inputNode.selectionEnd, 0, "selection starts/ends at 0");
is(inputValue, "", "Input value is cleared");
await waitFor(() => !jsterm.getInputValue());
is(jsterm.getInputValue(), "", "Input is cleared");
}
await hud.jsterm.execute("document.\nlocation.\nhref");
await jsterm.execute("document.\nlocation.\nhref");
checkEventTelemetry();
});
}
function checkEventTelemetry() {
const snapshot = Services.telemetry.snapshotEvents(OPTOUT, true);

View File

@ -11,27 +11,30 @@ const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
"test/mochitest/test-console.html";
add_task(async function() {
const hud = await openNewTabAndConsole(TEST_URI);
testCompletion(hud);
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
function testCompletion(hud) {
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const jsterm = hud.jsterm;
const input = jsterm.inputNode;
// Test typing 'var d = 5;' and press RETURN
jsterm.setInputValue("var d = ");
EventUtils.sendString("5;");
is(input.value, "var d = 5;", "var d = 5;");
is(jsterm.completeNode.value, "", "no completion");
is(jsterm.getInputValue(), "var d = 5;", "var d = 5;");
checkJsTermCompletionValue(jsterm, "", "no completion");
EventUtils.synthesizeKey("KEY_Enter");
is(jsterm.completeNode.value, "", "clear completion on execute()");
checkJsTermCompletionValue(jsterm, "", "clear completion on execute()");
// Test typing 'var a = d' and press RETURN
jsterm.setInputValue("var a = ");
EventUtils.sendString("d");
is(input.value, "var a = d", "var a = d");
is(jsterm.completeNode.value, "", "no completion");
is(jsterm.getInputValue(), "var a = d", "var a = d");
checkJsTermCompletionValue(jsterm, "", "no completion");
EventUtils.synthesizeKey("KEY_Enter");
is(jsterm.completeNode.value, "", "clear completion on execute()");
checkJsTermCompletionValue(jsterm, "", "clear completion on execute()");
}

View File

@ -10,19 +10,22 @@
const TEST_URI = "data:text/html,Testing jsterm with no input";
add_task(async function() {
const hud = await openNewTabAndConsole(TEST_URI);
testCompletion(hud);
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
function testCompletion(hud) {
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const jsterm = hud.jsterm;
const input = jsterm.inputNode;
// With empty input, tab through
jsterm.setInputValue("");
EventUtils.synthesizeKey("KEY_Tab");
is(jsterm.getInputValue(), "", "inputnode is empty - matched");
ok(!hasFocus(input), "input isn't focused anymore");
ok(!isJstermFocused(jsterm), "input isn't focused anymore");
jsterm.focus();
// With non-empty input, insert a tab
@ -30,5 +33,5 @@ function testCompletion(hud) {
EventUtils.synthesizeKey("KEY_Tab");
is(jsterm.getInputValue(), "window.Bug583816\t",
"input content - matched");
ok(hasFocus(input), "input is still focused");
ok(isJstermFocused(jsterm), "input is still focused");
}

View File

@ -7,6 +7,14 @@ const TEST_URI =
"data:text/html,Test evaluating null and undefined";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const jsterm = hud.jsterm;
@ -20,4 +28,4 @@ add_task(async function() {
jsterm.execute("undefined");
message = await onMessage;
ok(message, "`undefined` returned the expected value");
});
}

View File

@ -12,6 +12,14 @@ const TEST_URI = "data:text/html;charset=utf-8,<p>bug 900448 - autocomplete " +
const TEST_URI_NAVIGATE = "data:text/html;charset=utf-8,<p>testing autocomplete closes";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const popup = hud.jsterm.autocompletePopup;
const popupShown = once(popup, "popup-opened");
@ -24,4 +32,4 @@ add_task(async function() {
await addTab(TEST_URI_NAVIGATE);
ok(!popup.isOpen, "Popup closes on tab switch");
});
}

View File

@ -13,6 +13,14 @@ const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
const dpr = "--dpr 1";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
ok(hud, "web console opened");
@ -23,7 +31,7 @@ add_task(async function() {
// overflow
await createScrollbarOverflow();
await testFullpageClipboardScrollbar(hud);
});
}
async function testClipboard(hud) {
const command = `:screenshot --clipboard ${dpr}`;

View File

@ -14,13 +14,21 @@ const FileUtils = (ChromeUtils.import("resource://gre/modules/FileUtils.jsm", {}
const dpr = "--dpr 1";
add_task(async function() {
// Run test with legacy JsTerm
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
await addTab(TEST_URI);
const hud = await openConsole();
ok(hud, "web console opened");
await testFile(hud);
});
}
async function testFile(hud) {
// Test capture to file

View File

@ -351,9 +351,9 @@ function hasFocus(node) {
*
* @param {JsTerm} jsterm
* @param {String} value : The value to set the jsterm to.
* @param {Integer} caretIndexOffset : A number that will be added to value.length
* when setting the caret. A negative number will place the caret
* in (end - offset) position. Default to 0 (caret set at the end)
* @param {Integer} caretPosition : The index where to place the cursor. A negative
* number will place the caret at (value.length - offset) position.
* Default to value.length (caret set at the end).
* @param {Integer} completionType : One of the following jsterm property
* - COMPLETE_FORWARD
* - COMPLETE_BACKWARD
@ -363,11 +363,29 @@ function hasFocus(node) {
* Will default to COMPLETE_HINT_ONLY.
* @returns {Promise} resolves when the jsterm is completed.
*/
function jstermSetValueAndComplete(jsterm, value, caretIndexOffset = 0, completionType) {
const {inputNode} = jsterm;
inputNode.value = value;
const index = value.length + caretIndexOffset;
inputNode.setSelectionRange(index, index);
function jstermSetValueAndComplete(
jsterm,
value,
caretPosition = value.length,
completionType
) {
jsterm.setInputValue(value);
if (caretPosition < 0) {
caretPosition = value.length + caretPosition;
}
if (Number.isInteger(caretPosition)) {
if (jsterm.inputNode) {
const {inputNode} = jsterm;
inputNode.value = value;
inputNode.setSelectionRange(caretPosition, caretPosition);
}
if (jsterm.editor) {
jsterm.editor.setCursor(jsterm.editor.getPosition(caretPosition));
}
}
return jstermComplete(jsterm, completionType);
}
@ -391,6 +409,120 @@ function jstermComplete(jsterm, completionType = jsterm.COMPLETE_HINT_ONLY) {
return updated;
}
/**
* Checks if the jsterm has the expected completion value.
*
* @param {JsTerm} jsterm
* @param {String} expectedValue
* @param {String} assertionInfo: Description of the assertion passed to `is`.
*/
function checkJsTermCompletionValue(jsterm, expectedValue, assertionInfo) {
const completionValue = getJsTermCompletionValue(jsterm);
if (completionValue === null) {
ok(false, "Couldn't retrieve the completion value");
}
info(`Expects "${expectedValue}", is "${completionValue}"`);
if (jsterm.completeNode) {
is(completionValue, expectedValue, assertionInfo);
} else {
// CodeMirror jsterm doesn't need to add prefix-spaces.
is(completionValue, expectedValue.trim(), assertionInfo);
}
}
/**
* Checks if the cursor on jsterm is at the expected position.
*
* @param {JsTerm} jsterm
* @param {Integer} expectedCursorIndex
* @param {String} assertionInfo: Description of the assertion passed to `is`.
*/
function checkJsTermCursor(jsterm, expectedCursorIndex, assertionInfo) {
if (jsterm.inputNode) {
const {selectionStart, selectionEnd} = jsterm.inputNode;
is(selectionStart, expectedCursorIndex, assertionInfo);
ok(selectionStart === selectionEnd);
} else {
is(jsterm.editor.getCursor().ch, expectedCursorIndex, assertionInfo);
}
}
/**
* Checks the jsterm value and the cursor position given an expected string containing
* a "|" to indicate the expected cursor position.
*
* @param {JsTerm} jsterm
* @param {String} expectedStringWithCursor:
* String with a "|" to indicate the expected cursor position.
* For example, this is how you assert an empty value with the focus "|",
* and this indicates the value should be "test" and the cursor at the
* end of the input: "test|".
* @param {String} assertionInfo: Description of the assertion passed to `is`.
*/
function checkJsTermValueAndCursor(jsterm, expectedStringWithCursor, assertionInfo) {
info(`Checking jsterm state: \n${expectedStringWithCursor}`);
if (!expectedStringWithCursor.includes("|")) {
ok(false,
`expectedStringWithCursor must contain a "|" char to indicate cursor position`);
}
const inputValue = expectedStringWithCursor.replace("|", "");
is(jsterm.getInputValue(), inputValue, "jsterm has expected value");
if (jsterm.inputNode) {
is(jsterm.inputNode.selectionStart, jsterm.inputNode.selectionEnd);
is(jsterm.inputNode.selectionStart, expectedStringWithCursor.indexOf("|"),
assertionInfo);
} else {
const lines = expectedStringWithCursor.split("\n");
const lineWithCursor = lines.findIndex(line => line.includes("|"));
const {ch, line} = jsterm.editor.getCursor();
is(line, lineWithCursor, assertionInfo + " - correct line");
is(ch, lines[lineWithCursor].indexOf("|"), assertionInfo + " - correct ch");
}
}
/**
* Returns the jsterm completion value, whether there's CodeMirror enabled or not.
*
* @param {JsTerm} jsterm
* @returns {String}
*/
function getJsTermCompletionValue(jsterm) {
if (jsterm.completeNode) {
return jsterm.completeNode.value;
}
if (jsterm.editor) {
return jsterm.editor.getAutoCompletionText();
}
return null;
}
/**
* Returns a boolean indicating if the jsterm is focused, whether there's CodeMirror
* enabled or not.
*
* @param {JsTerm} jsterm
* @returns {Boolean}
*/
function isJstermFocused(jsterm) {
const document = jsterm.outputNode.ownerDocument;
const documentIsFocused = document.hasFocus();
if (jsterm.inputNode) {
return document.activeElement == jsterm.inputNode && documentIsFocused;
}
if (jsterm.editor) {
return documentIsFocused && jsterm.editor.hasFocus();
}
return false;
}
/**
* Open the JavaScript debugger.
*

View File

@ -18,7 +18,7 @@ hg clone http://hg.mozilla.org/mozilla-central
## Building and running locally
Fortunately, the Firefox team has made a very good job of automating the building process with bootstrap scripts and putting [documentation](https://developer.mozilla.org/En/Simple_Firefox_build) together.
Fortunately, the Firefox team has made a very good job of automating the building process with bootstrap scripts and putting [documentation](https://developer.mozilla.org/docs/Mozilla/Developer_guide/Build_Instructions/Simple_Firefox_build) together.
The very first time you are building Firefox, run:

View File

@ -26,7 +26,7 @@ PeriodicWave::PeriodicWave(AudioContext* aContext,
, mDisableNormalization(aDisableNormalization)
{
MOZ_ASSERT(aContext);
MOZ_ASSERT(aRealData || aImagData);
MOZ_ASSERT((aRealData || aImagData) || aLength == 2);
// Caller should have checked this and thrown.
MOZ_ASSERT(aLength > 0);
@ -44,21 +44,29 @@ PeriodicWave::PeriodicWave(AudioContext* aContext,
auto data = static_cast<float*>(buffer->Data());
mCoefficients.mBuffer = std::move(buffer);
if (aRealData) {
PodCopy(data, aRealData, aLength);
} else {
if (!aRealData && !aImagData) {
PodZero(data, aLength);
}
mCoefficients.mChannelData.AppendElement(data);
data += aLength;
if (aImagData) {
PodCopy(data, aImagData, aLength);
mCoefficients.mChannelData.AppendElement(data);
data += aLength;
data[0] = 0.0f;
data[1] = 1.0f;
mCoefficients.mChannelData.AppendElement(data);
} else {
PodZero(data, aLength);
}
mCoefficients.mChannelData.AppendElement(data);
if (aRealData) {
PodCopy(data, aRealData, aLength);
} else {
PodZero(data, aLength);
}
mCoefficients.mChannelData.AppendElement(data);
data += aLength;
if (aImagData) {
PodCopy(data, aImagData, aLength);
} else {
PodZero(data, aLength);
}
mCoefficients.mChannelData.AppendElement(data);
}
mCoefficients.mVolume = 1.0f;
mCoefficients.mBufferFormat = AUDIO_FORMAT_FLOAT32;
}
@ -69,10 +77,6 @@ PeriodicWave::Constructor(const GlobalObject& aGlobal,
const PeriodicWaveOptions& aOptions,
ErrorResult& aRv)
{
if (!aOptions.mReal.WasPassed() && !aOptions.mImag.WasPassed()) {
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
return nullptr;
}
if (aOptions.mReal.WasPassed() && aOptions.mImag.WasPassed() &&
aOptions.mReal.Value().Length() != aOptions.mImag.Value().Length()) {
@ -80,8 +84,17 @@ PeriodicWave::Constructor(const GlobalObject& aGlobal,
return nullptr;
}
uint32_t length =
aOptions.mReal.WasPassed() ? aOptions.mReal.Value().Length() : aOptions.mImag.Value().Length();
uint32_t length = 0;
if (aOptions.mReal.WasPassed()) {
length = aOptions.mReal.Value().Length();
} else if (aOptions.mImag.WasPassed()) {
length = aOptions.mImag.Value().Length();
} else {
// If nothing has been passed, this PeriodicWave will be a sine wave: 2
// elements for each array, the second imaginary component set to 1.0.
length = 2;
}
if (length == 0) {
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
return nullptr;

View File

@ -41,10 +41,9 @@ addLoadEvent(function() {
ac.createPeriodicWave(new Float32Array(4097), new Float32Array(4097));
});
// real || imag
expectException(function() {
expectNoException(function() {
new PeriodicWave(ac, {});
}, DOMException.INDEX_SIZE_ERR);
});
// real.size == imag.size
expectException(function() {

View File

@ -20,9 +20,6 @@ dictionary PeriodicWaveOptions : PeriodicWaveConstraints {
};
[Pref="dom.webaudio.enabled",
// XXXbz The second arg is not optional in the spec, but that looks
// like a spec bug to me. See
// <https://github.com/WebAudio/web-audio-api/issues/1116>.
Constructor(BaseAudioContext context, optional PeriodicWaveOptions options)]
interface PeriodicWave {
};

View File

@ -1233,6 +1233,34 @@ gfxTextRun::GetAdvanceWidth(Range aRange, PropertyProvider *aProvider,
return result + GetAdvanceForGlyphs(ligatureRange);
}
gfxFloat
gfxTextRun::GetMinAdvanceWidth(Range aRange)
{
MOZ_ASSERT(aRange.end <= GetLength(), "Substring out of range");
Range ligatureRange = aRange;
ShrinkToLigatureBoundaries(&ligatureRange);
gfxFloat result = std::max(
ComputePartialLigatureWidth(Range(aRange.start, ligatureRange.start),
nullptr),
ComputePartialLigatureWidth(Range(ligatureRange.end, aRange.end),
nullptr));
// XXX Do we need to take spacing into account? When each grapheme cluster
// takes its own line, we shouldn't be adding spacings around them.
gfxFloat clusterAdvance = 0;
for (uint32_t i = ligatureRange.start; i < ligatureRange.end; ++i) {
clusterAdvance += GetAdvanceForGlyph(i);
if (i + 1 == ligatureRange.end || IsClusterStart(i + 1)) {
result = std::max(result, clusterAdvance);
clusterAdvance = 0;
}
}
return result;
}
bool
gfxTextRun::SetLineBreaks(Range aRange,
bool aLineBreakBefore, bool aLineBreakAfter,

View File

@ -326,6 +326,12 @@ public:
return GetAdvanceWidth(Range(this), nullptr);
}
/**
* Computes the minimum advance width for a substring assuming line
* breaking is allowed everywhere.
*/
gfxFloat GetMinAdvanceWidth(Range aRange);
/**
* Clear all stored line breaks for the given range (both before and after),
* and then set the line-break state before aRange.start to aBreakBefore and

View File

@ -8549,6 +8549,16 @@ nsTextFrame::AddInlineMinISizeForFlow(gfxContext *aRenderingContext,
return;
}
// If overflow-wrap is break-word, we can wrap everywhere.
if (textStyle->WordCanWrap(this)) {
aData->OptionallyBreak();
aData->mCurrentLine +=
textRun->GetMinAdvanceWidth(Range(start, flowEndInTextRun));
aData->mTrailingWhitespace = 0;
aData->OptionallyBreak();
return;
}
AutoTArray<gfxTextRun::HyphenType, BIG_TEXT_NODE_SIZE> hyphBuffer;
if (hyphenating) {
if (hyphBuffer.AppendElements(flowEndInTextRun - start, fallible)) {

View File

@ -2234,6 +2234,7 @@ pref("network.proxy.autoconfig_url.include_path", false);
// until we reach interval_max or the PAC file is successfully loaded).
pref("network.proxy.autoconfig_retry_interval_min", 5); // 5 seconds
pref("network.proxy.autoconfig_retry_interval_max", 300); // 5 minutes
pref("network.proxy.enable_wpad_over_dhcp", true);
// Use the HSTS preload list by default
pref("network.stricttransportsecurity.preloadlist", true);

View File

@ -1043,4 +1043,4 @@ ProxyAutoConfig::MyIPAddress(const JS::CallArgs &aArgs)
}
} // namespace net
} // namespace mozilla
} // namespace mozilla

View File

@ -38,6 +38,7 @@ XPIDL_SOURCES += [
'nsIDashboard.idl',
'nsIDashboardEventNotifier.idl',
'nsIDeprecationWarner.idl',
'nsIDHCPClient.idl',
'nsIDivertableChannel.idl',
'nsIDownloader.idl',
'nsIEncodedChannel.idl',

View File

@ -0,0 +1,19 @@
/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
/**
* This interface allows the proxy code to access the DHCP Options in a platform-specific way
*/
[scriptable, uuid(aee75dc0-be1a-46b9-9e0c-31a6899c175c)]
interface nsIDHCPClient : nsISupports
{
/**
* returns the DHCP Option designated by the option parameter
*/
ACString getOption(in uint8_t option);
};

View File

@ -5,17 +5,20 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsPACMan.h"
#include "nsThreadUtils.h"
#include "mozilla/Preferences.h"
#include "nsContentUtils.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsIAuthPrompt.h"
#include "nsIPromptFactory.h"
#include "nsIDHCPClient.h"
#include "nsIHttpChannel.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsNetUtil.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsIPromptFactory.h"
#include "nsIProtocolProxyService.h"
#include "nsISystemProxySettings.h"
#include "nsContentUtils.h"
#include "mozilla/Preferences.h"
#include "nsNetUtil.h"
#include "nsThreadUtils.h"
//-----------------------------------------------------------------------------
@ -26,6 +29,8 @@ LazyLogModule gProxyLog("proxy");
#undef LOG
#define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args)
#define MOZ_WPAD_URL "http://wpad/wpad.dat"
#define MOZ_DHCP_WPAD_OPTION 252
// The PAC thread does evaluations of both PAC files and
// nsISystemProxySettings because they can both block the calling thread and we
@ -75,6 +80,26 @@ GetExtraJSContextHeapSize()
return extraSize < 0 ? 0 : extraSize;
}
// Read network proxy type from preference
// Used to verify that the preference is WPAD in nsPACMan::ConfigureWPAD
nsresult
GetNetworkProxyTypeFromPref(int32_t* type)
{
*type = 0;
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (!prefs) {
LOG(("Failed to get a preference service object"));
return NS_ERROR_FACTORY_NOT_REGISTERED;
}
nsresult rv = prefs->GetIntPref("network.proxy.type", type);
if (!NS_SUCCEEDED(rv)) {
LOG(("Failed to retrieve network.proxy.type from prefs"));
return rv;
}
LOG(("network.proxy.type pref retrieved: %d\n", *type));
return NS_OK;
}
//-----------------------------------------------------------------------------
@ -92,12 +117,12 @@ public:
{
}
void SetPACString(const nsCString &pacString)
void SetPACString(const nsACString &pacString)
{
mPACString = pacString;
}
void SetPACURL(const nsCString &pacURL)
void SetPACURL(const nsACString &pacURL)
{
mPACURL = pacURL;
}
@ -191,14 +216,43 @@ public:
}
private:
RefPtr<nsPACMan> mPACMan;
RefPtr<nsPACMan> mPACMan;
};
//-----------------------------------------------------------------------------
// ConfigureWPADComplete allows the PAC thread to tell the main thread that
// the URL for the PAC file has been found
class ConfigureWPADComplete final : public Runnable
{
public:
ConfigureWPADComplete(nsPACMan *aPACMan, const nsACString &aPACURISpec)
: Runnable("net::ConfigureWPADComplete"),
mPACMan(aPACMan),
mPACURISpec(aPACURISpec)
{
}
NS_IMETHOD Run() override
{
MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
mPACMan->AssignPACURISpec(mPACURISpec);
mPACMan->ContinueLoadingAfterPACUriKnown();
return NS_OK;
}
private:
RefPtr<nsPACMan> mPACMan;
nsCString mPACURISpec;
};
//-----------------------------------------------------------------------------
// ExecutePACThreadAction is used to proxy actions from the main
// thread onto the PAC thread. There are 3 options: process the queue,
// cancel the queue, and setup the javascript context with a new PAC file
// thread onto the PAC thread. There are 4 options: process the queue,
// cancel the queue, query DHCP for the PAC option
// and setup the javascript context with a new PAC file
class ExecutePACThreadAction final : public Runnable
{
@ -211,6 +265,7 @@ public:
, mCancelStatus(NS_OK)
, mSetupPAC(false)
, mExtraHeapSize(0)
, mConfigureWPAD(false)
{ }
void CancelQueue (nsresult status)
@ -221,7 +276,7 @@ public:
void SetupPAC (const char *text,
uint32_t datalen,
nsCString &pacURI,
const nsACString &pacURI,
uint32_t extraHeapSize)
{
mSetupPAC = true;
@ -230,6 +285,11 @@ public:
mExtraHeapSize = extraHeapSize;
}
void ConfigureWPAD()
{
mConfigureWPAD = true;
}
NS_IMETHOD Run() override
{
MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
@ -254,6 +314,15 @@ public:
return NS_OK;
}
if (mConfigureWPAD) {
nsAutoCString spec;
mConfigureWPAD = false;
mPACMan->ConfigureWPAD(spec);
RefPtr<ConfigureWPADComplete> runnable = new ConfigureWPADComplete(mPACMan, spec);
mPACMan->Dispatch(runnable.forget());
return NS_OK;
}
mPACMan->ProcessPendingQ();
return NS_OK;
}
@ -268,6 +337,7 @@ private:
uint32_t mExtraHeapSize;
nsCString mSetupPACData;
nsCString mSetupPACURI;
bool mConfigureWPAD;
};
//-----------------------------------------------------------------------------
@ -289,7 +359,7 @@ PendingPACQuery::PendingPACQuery(nsPACMan* pacMan,
}
void
PendingPACQuery::Complete(nsresult status, const nsCString &pacString)
PendingPACQuery::Complete(nsresult status, const nsACString &pacString)
{
if (!mCallback)
return;
@ -302,7 +372,7 @@ PendingPACQuery::Complete(nsresult status, const nsCString &pacString)
}
void
PendingPACQuery::UseAlternatePACFile(const nsCString &pacURL)
PendingPACQuery::UseAlternatePACFile(const nsACString &pacURL)
{
if (!mCallback)
return;
@ -337,6 +407,9 @@ nsPACMan::nsPACMan(nsIEventTarget *mainThreadEventTarget)
, mShutdown(false)
, mLoadFailureCount(0)
, mInProgress(false)
, mAutoDetect(false)
, mWPADOverDHCPEnabled(false)
, mProxyConfigType(0)
{
MOZ_ASSERT(NS_IsMainThread(), "pacman must be created on main thread");
if (!sThreadLocalSetup){
@ -354,6 +427,7 @@ nsPACMan::~nsPACMan()
if (mPACThread) {
if (NS_IsMainThread()) {
mPACThread->Shutdown();
mPACThread = nullptr;
}
else {
RefPtr<ShutdownThread> runnable = new ShutdownThread(mPACThread);
@ -396,7 +470,7 @@ nsPACMan::AsyncGetProxyForURI(nsIURI *uri,
TimeStamp::Now() > mScheduledReload) {
LOG(("nsPACMan::AsyncGetProxyForURI reload as scheduled\n"));
LoadPACFromURI(EmptyCString());
LoadPACFromURI(mAutoDetect? EmptyCString(): mPACURISpec);
}
RefPtr<PendingPACQuery> query =
@ -429,16 +503,15 @@ nsPACMan::PostQuery(PendingPACQuery *query)
}
nsresult
nsPACMan::LoadPACFromURI(const nsCString &spec)
nsPACMan::LoadPACFromURI(const nsACString &aSpec)
{
NS_ENSURE_STATE(!mShutdown);
NS_ENSURE_ARG(!spec.IsEmpty() || !mPACURISpec.IsEmpty());
nsCOMPtr<nsIStreamLoader> loader =
do_CreateInstance(NS_STREAMLOADER_CONTRACTID);
NS_ENSURE_STATE(loader);
LOG(("nsPACMan::LoadPACFromURI %s\n", spec.get()));
LOG(("nsPACMan::LoadPACFromURI aSpec: %s\n", aSpec.BeginReading()));
// Since we might get called from nsProtocolProxyService::Init, we need to
// post an event back to the main thread before we try to use the IO service.
//
@ -459,18 +532,67 @@ nsPACMan::LoadPACFromURI(const nsCString &spec)
CancelExistingLoad();
mLoader = loader;
if (!spec.IsEmpty()) {
mPACURISpec = spec;
mPACURIRedirectSpec.Truncate();
mNormalPACURISpec.Truncate(); // set at load time
mLoadFailureCount = 0; // reset
}
mPACURIRedirectSpec.Truncate();
mNormalPACURISpec.Truncate(); // set at load time
mLoadFailureCount = 0; // reset
mAutoDetect = aSpec.IsEmpty();
mPACURISpec.Assign(aSpec);
// reset to Null
mScheduledReload = TimeStamp();
return NS_OK;
}
nsresult
nsPACMan::GetPACFromDHCP(nsACString &aSpec)
{
MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
if (!mDHCPClient) {
LOG(("nsPACMan::GetPACFromDHCP DHCP option %d query failed because there is no DHCP client available\n", MOZ_DHCP_WPAD_OPTION));
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult rv;
rv = mDHCPClient->GetOption(MOZ_DHCP_WPAD_OPTION, aSpec);
if (NS_FAILED(rv)) {
LOG(("nsPACMan::GetPACFromDHCP DHCP option %d query failed with result %d\n", MOZ_DHCP_WPAD_OPTION, (uint32_t)rv));
} else {
LOG(("nsPACMan::GetPACFromDHCP DHCP option %d query succeeded, finding PAC URL %s\n", MOZ_DHCP_WPAD_OPTION, aSpec.BeginReading()));
}
return rv;
}
nsresult
nsPACMan::ConfigureWPAD(nsACString &aSpec)
{
MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
MOZ_RELEASE_ASSERT(mProxyConfigType == nsIProtocolProxyService::PROXYCONFIG_WPAD,
"WPAD is being executed when not selected by user");
aSpec.Truncate();
if (mWPADOverDHCPEnabled) {
GetPACFromDHCP(aSpec);
}
if (aSpec.IsEmpty()) {
// We diverge from the WPAD spec here in that we don't walk the
// hosts's FQDN, stripping components until we hit a TLD. Doing so
// is dangerous in the face of an incomplete list of TLDs, and TLDs
// get added over time. We could consider doing only a single
// substitution of the first component, if that proves to help
// compatibility.
aSpec.AssignLiteral(MOZ_WPAD_URL);
}
return NS_OK;
}
void
nsPACMan::AssignPACURISpec(const nsACString &aSpec)
{
MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
mPACURISpec.Assign(aSpec);
}
void
nsPACMan::StartLoading()
{
@ -483,6 +605,29 @@ nsPACMan::StartLoading()
return;
}
if (mAutoDetect) {
GetNetworkProxyTypeFromPref(&mProxyConfigType);
RefPtr<ExecutePACThreadAction> wpadConfigurer =
new ExecutePACThreadAction(this);
wpadConfigurer->ConfigureWPAD();
if (mPACThread) {
mPACThread->Dispatch(wpadConfigurer, nsIEventTarget::DISPATCH_NORMAL);
}
} else {
ContinueLoadingAfterPACUriKnown();
}
}
void
nsPACMan::ContinueLoadingAfterPACUriKnown()
{
MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
// CancelExistingLoad was called...
if (!mLoader) {
PostCancelPendingQ(NS_ERROR_ABORT);
return;
}
if (NS_SUCCEEDED(mLoader->Init(this, nullptr))) {
// Always hit the origin server when loading PAC.
nsCOMPtr<nsIIOService> ios = do_GetIOService();
@ -800,6 +945,7 @@ nsresult
nsPACMan::Init(nsISystemProxySettings *systemProxySettings)
{
mSystemProxySettings = systemProxySettings;
mDHCPClient = do_GetService(NS_DHCPCLIENT_CONTRACTID);
nsresult rv =
NS_NewNamedThread("ProxyResolution", getter_AddRefs(mPACThread));
@ -808,4 +954,4 @@ nsPACMan::Init(nsISystemProxySettings *systemProxySettings)
}
} // namespace net
} // namespace mozilla
} // namespace mozilla

View File

@ -7,23 +7,24 @@
#ifndef nsPACMan_h__
#define nsPACMan_h__
#include "nsIStreamLoader.h"
#include "nsIInterfaceRequestor.h"
#include "nsIChannelEventSink.h"
#include "ProxyAutoConfig.h"
#include "nsThreadUtils.h"
#include "nsIURI.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/LinkedList.h"
#include "nsAutoPtr.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Logging.h"
#include "mozilla/Atomics.h"
#include "mozilla/net/NeckoTargetHolder.h"
#include "mozilla/TimeStamp.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsIChannelEventSink.h"
#include "nsIInterfaceRequestor.h"
#include "nsIStreamLoader.h"
#include "nsThreadUtils.h"
#include "nsIURI.h"
#include "nsString.h"
#include "ProxyAutoConfig.h"
class nsISystemProxySettings;
class nsIDHCPClient;
class nsIThread;
namespace mozilla {
@ -52,8 +53,8 @@ public:
* newPACURL should be 0 length.
*/
virtual void OnQueryComplete(nsresult status,
const nsCString &pacString,
const nsCString &newPACURL) = 0;
const nsACString &pacString,
const nsACString &newPACURL) = 0;
};
class PendingPACQuery final : public Runnable,
@ -65,8 +66,8 @@ public:
bool mainThreadResponse);
// can be called from either thread
void Complete(nsresult status, const nsCString &pacString);
void UseAlternatePACFile(const nsCString &pacURL);
void Complete(nsresult status, const nsACString &pacString);
void UseAlternatePACFile(const nsACString &pacURL);
nsCString mSpec;
nsCString mScheme;
@ -127,11 +128,11 @@ public:
* the PAC file, any asynchronous PAC queries will be queued up to be
* processed once the PAC file finishes loading.
*
* @param pacSpec
* @param aSpec
* The non normalized uri spec of this URI used for comparison with
* system proxy settings to determine if the PAC uri has changed.
*/
nsresult LoadPACFromURI(const nsCString &pacSpec);
nsresult LoadPACFromURI(const nsACString &aSpec);
/**
* Returns true if we are currently loading the PAC file.
@ -166,6 +167,10 @@ public:
return IsPACURI(tmp);
}
bool IsUsingWPAD() {
return mAutoDetect;
}
nsresult Init(nsISystemProxySettings *);
static nsPACMan *sInstance;
@ -173,6 +178,8 @@ public:
void ProcessPendingQ();
void CancelPendingQ(nsresult);
void SetWPADOverDHCPEnabled(bool aValue) { mWPADOverDHCPEnabled = aValue; }
private:
NS_DECL_NSISTREAMLOADEROBSERVER
NS_DECL_NSIINTERFACEREQUESTOR
@ -180,8 +187,10 @@ private:
friend class PendingPACQuery;
friend class PACLoadComplete;
friend class ConfigureWPADComplete;
friend class ExecutePACThreadAction;
friend class WaitForThreadShutdown;
friend class TestPACMan;
~nsPACMan();
@ -195,6 +204,11 @@ private:
*/
void StartLoading();
/**
* Continue loading the PAC file.
*/
void ContinueLoadingAfterPACUriKnown();
/**
* Reload the PAC file if there is reason to.
*/
@ -212,15 +226,22 @@ private:
*/
nsresult PostQuery(PendingPACQuery *query);
// Having found the PAC URI on the PAC thread, copy it to a string which
// can be altered on the main thread.
void AssignPACURISpec(const nsACString &aSpec);
// PAC thread operations only
void PostProcessPendingQ();
void PostCancelPendingQ(nsresult);
bool ProcessPending();
nsresult GetPACFromDHCP(nsACString &aSpec);
nsresult ConfigureWPAD(nsACString &aSpec);
private:
ProxyAutoConfig mPAC;
nsCOMPtr<nsIThread> mPACThread;
nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
nsCOMPtr<nsIDHCPClient> mDHCPClient;
LinkedList<PendingPACQuery> mPendingQ; /* pac thread only */
@ -239,6 +260,9 @@ private:
bool mInProgress;
bool mIncludePath;
bool mAutoDetect;
bool mWPADOverDHCPEnabled;
int32_t mProxyConfigType;
};
extern LazyLogModule gProxyLog;
@ -246,4 +270,4 @@ extern LazyLogModule gProxyLog;
} // namespace net
} // namespace mozilla
#endif // nsPACMan_h__
#endif // nsPACMan_h__

View File

@ -62,7 +62,6 @@ namespace net {
#define PROXY_PREF_BRANCH "network.proxy"
#define PROXY_PREF(x) PROXY_PREF_BRANCH "." x
#define WPAD_URL "http://wpad/wpad.dat"
//----------------------------------------------------------------------------
@ -311,8 +310,8 @@ private:
// Called asynchronously, so we do not need to post another PLEvent
// before calling DoCallback.
void OnQueryComplete(nsresult status,
const nsCString &pacString,
const nsCString &newPACURL) override
const nsACString &pacString,
const nsACString &newPACURL) override
{
// If we've already called DoCallback then, nothing more to do.
if (!mCallback)
@ -821,6 +820,7 @@ nsProtocolProxyService::nsProtocolProxyService()
, mSOCKSProxyVersion(4)
, mSOCKSProxyRemoteDNS(false)
, mProxyOverTLS(true)
, mWPADOverDHCPEnabled(false)
, mPACMan(nullptr)
, mSessionStart(PR_Now())
, mFailedProxyTimeout(30 * 60) // 30 minute default
@ -1087,6 +1087,12 @@ nsProtocolProxyService::PrefsChanged(nsIPrefBranch *prefBranch,
mProxyOverTLS);
}
if (!pref || !strcmp(pref, PROXY_PREF("enable_wpad_over_dhcp"))) {
proxy_GetBoolPref(prefBranch, PROXY_PREF("enable_wpad_over_dhcp"),
mWPADOverDHCPEnabled);
reloadPAC = reloadPAC || mProxyConfig == PROXYCONFIG_WPAD;
}
if (!pref || !strcmp(pref, PROXY_PREF("failover_timeout")))
proxy_GetIntPref(prefBranch, PROXY_PREF("failover_timeout"),
mFailedProxyTimeout);
@ -1119,19 +1125,15 @@ nsProtocolProxyService::PrefsChanged(nsIPrefBranch *prefBranch,
ResetPACThread();
}
} else if (mProxyConfig == PROXYCONFIG_WPAD) {
// We diverge from the WPAD spec here in that we don't walk the
// hosts's FQDN, stripping components until we hit a TLD. Doing so
// is dangerous in the face of an incomplete list of TLDs, and TLDs
// get added over time. We could consider doing only a single
// substitution of the first component, if that proves to help
// compatibility.
tempString.AssignLiteral(WPAD_URL);
LOG(("Auto-detecting proxy - Reset Pac Thread"));
ResetPACThread();
} else if (mSystemProxySettings) {
// Get System Proxy settings if available
AsyncConfigureFromPAC(false, false);
}
if (!tempString.IsEmpty())
if (!tempString.IsEmpty() || mProxyConfig == PROXYCONFIG_WPAD) {
ConfigureFromPAC(tempString, false);
}
}
}
@ -1482,7 +1484,6 @@ nsProtocolProxyService::SetupPACThread(nsIEventTarget *mainThreadEventTarget)
else {
rv = mPACMan->Init(nullptr);
}
if (NS_FAILED(rv)) {
mPACMan->Shutdown();
mPACMan = nullptr;
@ -1508,11 +1509,15 @@ nsProtocolProxyService::ConfigureFromPAC(const nsCString &spec,
nsresult rv = SetupPACThread();
NS_ENSURE_SUCCESS(rv, rv);
if (mPACMan->IsPACURI(spec) && !forceReload)
bool autodetect = spec.IsEmpty();
if (!forceReload && ((!autodetect && mPACMan->IsPACURI(spec)) ||
(autodetect && mPACMan->IsUsingWPAD()))) {
return NS_OK;
}
mFailedProxies.Clear();
mPACMan->SetWPADOverDHCPEnabled(mWPADOverDHCPEnabled);
return mPACMan->LoadPACFromURI(spec);
}
@ -1565,8 +1570,6 @@ nsProtocolProxyService::ReloadPAC()
nsAutoCString pacSpec;
if (type == PROXYCONFIG_PAC)
prefs->GetCharPref(PROXY_PREF("autoconfig_url"), pacSpec);
else if (type == PROXYCONFIG_WPAD)
pacSpec.AssignLiteral(WPAD_URL);
else if (type == PROXYCONFIG_SYSTEM) {
if (mSystemProxySettings) {
AsyncConfigureFromPAC(true, true);
@ -1575,7 +1578,7 @@ nsProtocolProxyService::ReloadPAC()
}
}
if (!pacSpec.IsEmpty())
if (!pacSpec.IsEmpty() || type == PROXYCONFIG_WPAD)
ConfigureFromPAC(pacSpec, true);
return NS_OK;
}
@ -1599,8 +1602,8 @@ class nsAsyncBridgeRequest final : public nsPACManCallback
}
void OnQueryComplete(nsresult status,
const nsCString &pacString,
const nsCString &newPACURL) override
const nsACString &pacString,
const nsACString &newPACURL) override
{
MutexAutoLock lock(mMutex);
mCompleted = true;
@ -2546,4 +2549,4 @@ nsProtocolProxyService::PruneProxyInfo(const nsProtocolInfo &info,
}
} // namespace net
} // namespace mozilla
} // namespace mozilla

View File

@ -400,6 +400,7 @@ protected:
int32_t mSOCKSProxyVersion;
bool mSOCKSProxyRemoteDNS;
bool mProxyOverTLS;
bool mWPADOverDHCPEnabled;
RefPtr<nsPACMan> mPACMan; // non-null if we are using PAC
nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
@ -423,4 +424,4 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsProtocolProxyService, NS_PROTOCOL_PROXY_SERVICE_
} // namespace net
} // namespace mozilla
#endif // !nsProtocolProxyService_h__
#endif // !nsProtocolProxyService_h__

View File

@ -335,6 +335,10 @@
#define NS_SYSTEMPROXYSETTINGS_CONTRACTID \
"@mozilla.org/system-proxy-settings;1"
// component implementing nsIDHCPClient.
#define NS_DHCPCLIENT_CONTRACTID \
"@mozilla.org/dhcp-client;1"
// service implementing nsIStreamTransportService
#define NS_STREAMTRANSPORTSERVICE_CONTRACTID \
"@mozilla.org/network/stream-transport-service;1"

View File

@ -0,0 +1,282 @@
#include "gtest/gtest.h"
#include "nsServiceManagerUtils.h"
#include "../../../xpcom/threads/nsThreadManager.h"
#include "nsIDHCPClient.h"
#include "nsIPrefBranch.h"
#include "nsComponentManager.h"
#include "mozilla/ModuleUtils.h"
#include "../../base/nsPACMan.h"
#define TEST_WPAD_DHCP_OPTION "http://pac/pac.dat"
#define TEST_ASSIGNED_PAC_URL "http://assignedpac/pac.dat"
#define WPAD_PREF 4
#define NETWORK_PROXY_TYPE_PREF_NAME "network.proxy.type"
#define GETTING_NETWORK_PROXY_TYPE_FAILED -1
nsCString WPADOptionResult;
namespace mozilla {
namespace net {
nsresult
SetNetworkProxyType(int32_t pref)
{
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (!prefs) {
return NS_ERROR_FACTORY_NOT_REGISTERED;
}
return prefs->SetIntPref(NETWORK_PROXY_TYPE_PREF_NAME, pref);
}
nsresult
GetNetworkProxyType(int32_t* pref)
{
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (!prefs) {
return NS_ERROR_FACTORY_NOT_REGISTERED;
}
return prefs->GetIntPref(NETWORK_PROXY_TYPE_PREF_NAME, pref);
}
class nsTestDHCPClient final : public nsIDHCPClient
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIDHCPCLIENT
nsTestDHCPClient() {};
nsresult Init(){
return NS_OK;
};
private:
~nsTestDHCPClient() {};
};
NS_IMETHODIMP
nsTestDHCPClient::GetOption(uint8_t option, nsACString & _retval)
{
_retval.Assign(WPADOptionResult);
return NS_OK;
}
NS_IMPL_ISUPPORTS(nsTestDHCPClient, nsIDHCPClient)
#define NS_TESTDHCPCLIENTSERVICE_CID /* {FEBF1D69-4D7D-4891-9524-045AD18B5592} */\
{ 0xFEBF1D69, 0x4D7D, 0x4891, \
{0x95, 0x24, 0x04, 0x5a, 0xd1, 0x8b, 0x55, 0x92 } }
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsTestDHCPClient, Init)
NS_DEFINE_NAMED_CID(NS_TESTDHCPCLIENTSERVICE_CID);
static const mozilla::Module::CIDEntry kSysDHCPClientCIDs[] = {
{ &kNS_TESTDHCPCLIENTSERVICE_CID, false, nullptr, nsTestDHCPClientConstructor },
{ nullptr }
};
static const mozilla::Module::ContractIDEntry kSysDHCPClientContracts[] = {
{ NS_DHCPCLIENT_CONTRACTID, &kNS_TESTDHCPCLIENTSERVICE_CID },
{ nullptr }
};
static const mozilla::Module kSysDHCPClientModule = {
mozilla::Module::kVersion,
kSysDHCPClientCIDs,
kSysDHCPClientContracts
};
NSMODULE_DEFN(nsDHCPClientModule) = &kSysDHCPClientModule;
void
SetOptionResult(const char* result)
{
WPADOptionResult.Assign(result);
}
class ProcessPendingEventsAction final : public Runnable
{
public:
ProcessPendingEventsAction() : Runnable("net::ProcessPendingEventsAction") { }
NS_IMETHOD
Run() override
{
if (NS_HasPendingEvents(nullptr)) {
NS_WARNING("Found pending requests on PAC thread");
nsresult rv;
rv = NS_ProcessPendingEvents(nullptr);
EXPECT_EQ(NS_OK, rv);
}
NS_WARNING("No pending requests on PAC thread");
return NS_OK;
}
};
class TestPACMan : public ::testing::Test {
protected:
RefPtr<nsPACMan> mPACMan;
void
ProcessAllEvents()
{
ProcessPendingEventsOnPACThread();
nsresult rv;
while (NS_HasPendingEvents(nullptr)) {
NS_WARNING("Pending events on main thread");
rv = NS_ProcessPendingEvents(nullptr);
ASSERT_EQ(NS_OK, rv);
ProcessPendingEventsOnPACThread();
}
NS_WARNING("End of pending events on main thread");
}
// This method is used to ensure that all pending events on the main thread
// and the Proxy thread are processsed.
// It iterates over ProcessAllEvents because simply calling ProcessAllEvents once
// did not reliably process the events on both threads on all platforms.
void
ProcessAllEventsTenTimes(){
for (int i = 0; i < 10; i++) {
ProcessAllEvents();
}
}
virtual void
SetUp()
{
ASSERT_EQ(NS_OK, GetNetworkProxyType(&originalNetworkProxyTypePref));
nsFactoryEntry* factoryEntry = nsComponentManagerImpl::gComponentManager
->GetFactoryEntry(kNS_TESTDHCPCLIENTSERVICE_CID);
if (factoryEntry) {
nsresult rv = nsComponentManagerImpl::gComponentManager->UnregisterFactory(kNS_TESTDHCPCLIENTSERVICE_CID, factoryEntry->mFactory);
ASSERT_EQ(NS_OK, rv);
}
nsComponentManagerImpl::gComponentManager->RegisterModule(&kSysDHCPClientModule, nullptr);
mPACMan = new nsPACMan(nullptr);
mPACMan->SetWPADOverDHCPEnabled(true);
mPACMan->Init(nullptr);
ASSERT_EQ(NS_OK, SetNetworkProxyType(WPAD_PREF));
}
virtual void
TearDown()
{
mPACMan->Shutdown();
if (originalNetworkProxyTypePref != GETTING_NETWORK_PROXY_TYPE_FAILED) {
ASSERT_EQ(NS_OK, SetNetworkProxyType(originalNetworkProxyTypePref));
}
}
nsCOMPtr<nsIDHCPClient>
GetPACManDHCPCient()
{
return mPACMan->mDHCPClient;
}
void
SetPACManDHCPCient(nsCOMPtr<nsIDHCPClient> aValue)
{
mPACMan->mDHCPClient = aValue;
}
void
AssertPACSpecEqualTo(const char* aExpected)
{
ASSERT_STREQ(aExpected, mPACMan->mPACURISpec.Data());
}
private:
int32_t originalNetworkProxyTypePref = GETTING_NETWORK_PROXY_TYPE_FAILED;
void ProcessPendingEventsOnPACThread(){
RefPtr<ProcessPendingEventsAction> action =
new ProcessPendingEventsAction();
mPACMan->mPACThread->Dispatch(action, nsIEventTarget::DISPATCH_SYNC);
}
};
TEST_F(TestPACMan, TestCreateDHCPClientAndGetOption) {
SetOptionResult(TEST_WPAD_DHCP_OPTION);
nsCString spec;
GetPACManDHCPCient()->GetOption(252, spec);
ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, spec.Data());
}
TEST_F(TestPACMan, TestCreateDHCPClientAndGetEmptyOption) {
SetOptionResult("");
nsCString spec;
spec.AssignLiteral(TEST_ASSIGNED_PAC_URL);
GetPACManDHCPCient()->GetOption(252, spec);
ASSERT_TRUE(spec.IsEmpty());
}
TEST_F(TestPACMan, WhenTheDHCPClientExistsAndDHCPIsNonEmptyDHCPOptionIsUsedAsPACUri) {
SetOptionResult(TEST_WPAD_DHCP_OPTION);
mPACMan->LoadPACFromURI(EmptyCString());
ProcessAllEventsTenTimes();
ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data());
AssertPACSpecEqualTo(TEST_WPAD_DHCP_OPTION);
}
TEST_F(TestPACMan, WhenTheDHCPResponseIsEmptyWPADDefaultsToStandardURL) {
SetOptionResult(EmptyCString().Data());
mPACMan->LoadPACFromURI(EmptyCString());
ASSERT_TRUE(NS_HasPendingEvents(nullptr));
ProcessAllEventsTenTimes();
ASSERT_STREQ("", WPADOptionResult.Data());
AssertPACSpecEqualTo("http://wpad/wpad.dat");
}
TEST_F(TestPACMan, WhenThereIsNoDHCPClientWPADDefaultsToStandardURL) {
SetOptionResult(TEST_WPAD_DHCP_OPTION);
SetPACManDHCPCient(nullptr);
mPACMan->LoadPACFromURI(EmptyCString());
ProcessAllEventsTenTimes();
ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data());
AssertPACSpecEqualTo("http://wpad/wpad.dat");
}
TEST_F(TestPACMan, WhenWPADOverDHCPIsPreffedOffWPADDefaultsToStandardURL) {
SetOptionResult(TEST_WPAD_DHCP_OPTION);
mPACMan->SetWPADOverDHCPEnabled(false);
mPACMan->LoadPACFromURI(EmptyCString());
ProcessAllEventsTenTimes();
ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data());
AssertPACSpecEqualTo("http://wpad/wpad.dat");
}
TEST_F(TestPACMan, WhenPACUriIsSetDirectlyItIsUsedRatherThanWPAD) {
SetOptionResult(TEST_WPAD_DHCP_OPTION);
nsCString spec;
spec.AssignLiteral(TEST_ASSIGNED_PAC_URL);
mPACMan->LoadPACFromURI(spec);
ProcessAllEventsTenTimes();
AssertPACSpecEqualTo(TEST_ASSIGNED_PAC_URL);
}
} // namespace net
} // namespace mozilla

View File

@ -10,6 +10,7 @@ UNIFIED_SOURCES += [
'TestHttpAuthUtils.cpp',
'TestMIMEInputStream.cpp',
'TestMozURL.cpp',
'TestPACMan.cpp',
'TestPartiallySeekableInputStream.cpp',
'TestProtocolProxyService.cpp',
'TestReadStreamToString.cpp',
@ -30,3 +31,8 @@ LOCAL_INCLUDES += [
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul-gtest'
LOCAL_INCLUDES += [
'!/xpcom',
'/xpcom/components'
]

View File

@ -430,8 +430,15 @@ class GitRepository(Repository):
def working_directory_clean(self, untracked=False, ignored=False):
args = ['status', '--porcelain']
if not untracked:
# Even in --porcelain mode, behavior is affected by the
# ``status.showUntrackedFiles`` option, which means we need to be
# explicit about how to treat untracked files.
if untracked:
args.append('--untracked-files=all')
else:
args.append('--untracked-files=no')
if ignored:
args.append('--ignored')

View File

@ -31,6 +31,7 @@ class MachCommands(MachCommandBase):
@Command('python-safety', category='testing',
description='Run python requirements safety checks')
@CommandArgument('--python',
default='2.7',
help='Version of Python for Pipenv to use. When given a '
'Python version, Pipenv will automatically scan your '
'system for a Python that matches that given version.')
@ -38,9 +39,7 @@ class MachCommands(MachCommandBase):
self.logger = commandline.setup_logging(
"python-safety", {"raw": sys.stdout})
python = python or self.virtualenv_manager.python_path
self.activate_pipenv(pipfile=os.path.join(here, 'Pipfile'), args=[
'--python', python], populate=True)
self.activate_pipenv(pipfile=os.path.join(here, 'Pipfile'), python=python, populate=True)
pattern = '**/*requirements*.txt'
path = mozpath.normsep(os.path.dirname(os.path.dirname(here)))

View File

@ -1165,4 +1165,4 @@ static const TransportSecurityPreload kPublicKeyPinningPreloadList[] = {
static const int32_t kUnknownId = -1;
static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1539861752318000);
static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1540232248625000);

View File

@ -8,7 +8,7 @@
/*****************************************************************************/
#include <stdint.h>
const PRTime gPreloadListExpirationTime = INT64_C(1542280764880000);
const PRTime gPreloadListExpirationTime = INT64_C(1542651331206000);
%%
0-1.party, 1
0.me.uk, 1
@ -2807,6 +2807,8 @@ appmobile.io, 1
appninjas.com, 1
apponic.com, 1
apponline.com, 1
apprenticeship.gov, 1
apprenticeships.gov, 1
approbo.com, 1
approvedtreecare.com, 1
apps.co, 1

View File

@ -644,6 +644,9 @@ static const char flashPluginSandboxRules[] = R"SANDBOX_LITERAL(
(global-name "com.apple.inputmethodkit.getxpcendpoint")
(global-name "com.apple.decalog4.incoming")
(global-name "com.apple.windowserver.active"))
; bug 1475707
(if (= macosMinorVersion 9)
(allow mach-lookup (global-name "com.apple.xpcd")))
; Fonts
(allow file-read*

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,6 @@ transforms:
- taskgraph.transforms.build_attrs:transforms
- taskgraph.transforms.build_lints:transforms
- taskgraph.transforms.use_toolchains:transforms
- taskgraph.transforms.use_fetches:transforms
- taskgraph.transforms.job:transforms
- taskgraph.transforms.task:transforms

View File

@ -7,7 +7,6 @@ kind-dependencies:
transforms:
- taskgraph.transforms.tests:transforms
- taskgraph.transforms.use_fetches:transforms
- taskgraph.transforms.job:transforms
- taskgraph.transforms.coalesce:transforms
- taskgraph.transforms.task:transforms

View File

@ -9,7 +9,6 @@ kind-dependencies:
transforms:
- taskgraph.transforms.try_job:transforms
- taskgraph.transforms.use_fetches:transforms
- taskgraph.transforms.use_toolchains:transforms
- taskgraph.transforms.job:transforms
- taskgraph.transforms.task:transforms

View File

@ -77,6 +77,9 @@ job_description_schema = Schema({
Optional('files-changed'): [basestring],
}),
# A list of artifacts to install from 'fetch' tasks.
Optional('fetches'): [basestring],
# A description of how to run this job.
'run': {
# The key to a job implementation in a peer module to this one
@ -128,6 +131,54 @@ def rewrite_when_to_optimization(config, jobs):
yield job
def get_attribute(dict, key, attributes, attribute_name):
'''Get `attribute_name` from the given `attributes` dict, and if there
is a corresponding value, set `key` in `dict` to that value.'''
value = attributes.get(attribute_name)
if value:
dict[key] = value
@transforms.add
def use_fetches(config, jobs):
artifacts = {}
for task in config.kind_dependencies_tasks:
if task.kind != 'fetch':
continue
name = task.label.replace('%s-' % task.kind, '')
get_attribute(artifacts, name, task.attributes, 'fetch-artifact')
for job in jobs:
fetches = job.pop('fetches', [])
# Hack added for `mach artifact toolchain` to support reading toolchain
# kinds in isolation.
if config.params.get('ignore_fetches'):
fetches[:] = []
for fetch in fetches:
if fetch not in artifacts:
raise Exception('Missing fetch job for %s-%s: %s' % (
config.kind, job['name'], fetch))
if not artifacts[fetch].startswith('public/'):
raise Exception('non-public artifacts not supported')
if fetches:
job.setdefault('dependencies', {}).update(
('fetch-%s' % f, 'fetch-%s' % f)
for f in fetches)
env = job.setdefault('worker', {}).setdefault('env', {})
env['MOZ_FETCHES'] = {'task-reference': ' '.join(
'%s@<fetch-%s>' % (artifacts[f], f)
for f in fetches)}
yield job
@transforms.add
def make_task_description(config, jobs):
"""Given a build description, create a task description"""

View File

@ -1,60 +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/.
from __future__ import absolute_import, unicode_literals
from .base import (
TransformSequence,
)
transforms = TransformSequence()
def get_attribute(dict, key, attributes, attribute_name):
'''Get `attribute_name` from the given `attributes` dict, and if there
is a corresponding value, set `key` in `dict` to that value.'''
value = attributes.get(attribute_name)
if value:
dict[key] = value
@transforms.add
def use_fetches(config, jobs):
artifacts = {}
for task in config.kind_dependencies_tasks:
if task.kind != 'fetch':
continue
name = task.label.replace('%s-' % task.kind, '')
get_attribute(artifacts, name, task.attributes, 'fetch-artifact')
for job in jobs:
fetches = job.pop('fetches', [])
# Hack added for `mach artifact toolchain` to support reading toolchain
# kinds in isolation.
if config.params.get('ignore_fetches'):
fetches[:] = []
for fetch in fetches:
if fetch not in artifacts:
raise Exception('Missing fetch job for %s-%s: %s' % (
config.kind, job['name'], fetch))
if not artifacts[fetch].startswith('public/'):
raise Exception('non-public artifacts not supported')
if fetches:
job.setdefault('dependencies', {}).update(
('fetch-%s' % f, 'fetch-%s' % f)
for f in fetches)
env = job.setdefault('worker', {}).setdefault('env', {})
env['MOZ_FETCHES'] = {'task-reference': ' '.join(
'%s@<fetch-%s>' % (artifacts[f], f)
for f in fetches)}
yield job

View File

@ -6,8 +6,7 @@
from __future__ import absolute_import, print_function
import BaseHTTPServer
import SimpleHTTPServer
import errno
import logging
import threading
@ -15,15 +14,22 @@ import posixpath
import socket
import sys
import os
import urllib
import urlparse
import re
import moznetwork
import time
from SocketServer import ThreadingMixIn
from six import iteritems
from six.moves.socketserver import ThreadingMixIn
from six.moves.BaseHTTPServer import HTTPServer
from six.moves.urllib.parse import (
urlsplit,
unquote,
)
from six.moves.SimpleHTTPServer import SimpleHTTPRequestHandler
class EasyServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
class EasyServer(ThreadingMixIn, HTTPServer):
allow_reuse_address = True
acceptable_errors = (errno.EPIPE, errno.ECONNABORTED)
@ -50,7 +56,7 @@ class Request(object):
def __init__(self, uri, headers, rfile=None):
self.uri = uri
self.headers = headers
parsed = urlparse.urlsplit(uri)
parsed = urlsplit(uri)
for i, attr in enumerate(self.uri_attrs):
setattr(self, attr, parsed[i])
try:
@ -63,7 +69,7 @@ class Request(object):
self.body = None
class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
class RequestHandler(SimpleHTTPRequestHandler):
docroot = os.getcwd() # current working directory at time of import
proxy_host_dirs = False
@ -72,7 +78,7 @@ class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
request = None
def __init__(self, *args, **kwargs):
SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs)
SimpleHTTPRequestHandler.__init__(self, *args, **kwargs)
self.extensions_map['.svg'] = 'image/svg+xml'
def _try_handler(self, method):
@ -89,7 +95,7 @@ class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
(response_code, headerdict, data) = \
handler['function'](self.request, *m.groups())
self.send_response(response_code)
for (keyword, value) in headerdict.iteritems():
for (keyword, value) in iteritems(headerdict):
self.send_header(keyword, value)
self.end_headers()
self.wfile.write(data)
@ -102,9 +108,9 @@ class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
"""Find the on-disk path to serve this request from,
using self.path_mappings and self.docroot.
Return (url_path, disk_path)."""
path_components = filter(None, self.request.path.split('/'))
for prefix, disk_path in self.path_mappings.iteritems():
prefix_components = filter(None, prefix.split('/'))
path_components = list(filter(None, self.request.path.split('/')))
for prefix, disk_path in iteritems(self.path_mappings):
prefix_components = list(filter(None, prefix.split('/')))
if len(path_components) < len(prefix_components):
continue
if path_components[:len(prefix_components)] == prefix_components:
@ -115,7 +121,7 @@ class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
return None
def parse_request(self):
retval = SimpleHTTPServer.SimpleHTTPRequestHandler.parse_request(self)
retval = SimpleHTTPRequestHandler.parse_request(self)
self.request = Request(self.path, self.headers, self.rfile)
return retval
@ -129,7 +135,7 @@ class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
if self.request.netloc and self.proxy_host_dirs:
self.path = '/' + self.request.netloc + \
self.path
SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
SimpleHTTPRequestHandler.do_GET(self)
else:
self.send_response(404)
self.end_headers()
@ -158,9 +164,9 @@ class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
# except we serve from self.docroot instead of os.getcwd(), and
# parse_request()/do_GET() have already stripped the query string and
# fragment and mangled the path for proxying, if required.
path = posixpath.normpath(urllib.unquote(self.path))
path = posixpath.normpath(unquote(self.path))
words = path.split('/')
words = filter(None, words)
words = list(filter(None, words))
path = self.disk_root
for word in words:
drive, word = os.path.splitdrive(word)

View File

@ -7,7 +7,7 @@ from __future__ import absolute_import
from setuptools import setup
PACKAGE_VERSION = '0.7'
deps = ['moznetwork >= 0.24']
deps = ['moznetwork >= 0.24', 'mozinfo >= 1.0.0', 'six >= 1.10.0']
setup(name='mozhttpd',
version=PACKAGE_VERSION,

View File

@ -8,7 +8,6 @@ from __future__ import absolute_import
import mozfile
import mozhttpd
import urllib2
import os
import unittest
import json
@ -16,6 +15,16 @@ import tempfile
import mozunit
from six.moves.urllib.request import (
HTTPHandler,
ProxyHandler,
Request,
build_opener,
install_opener,
urlopen,
)
from six.moves.urllib.error import HTTPError
here = os.path.dirname(os.path.abspath(__file__))
@ -54,7 +63,7 @@ class ApiTest(unittest.TestCase):
def try_get(self, server_port, querystr):
self.resource_get_called = 0
f = urllib2.urlopen(self.get_url('/api/resource/1', server_port, querystr))
f = urlopen(self.get_url('/api/resource/1', server_port, querystr))
try:
self.assertEqual(f.getcode(), 200)
except AttributeError:
@ -67,9 +76,11 @@ class ApiTest(unittest.TestCase):
postdata = {'hamburgers': '1234'}
try:
f = urllib2.urlopen(self.get_url('/api/resource/', server_port, querystr),
data=json.dumps(postdata))
except urllib2.HTTPError as e:
f = urlopen(
self.get_url('/api/resource/', server_port, querystr),
data=json.dumps(postdata),
)
except HTTPError as e:
# python 2.4
self.assertEqual(e.code, 201)
body = e.fp.read()
@ -84,8 +95,8 @@ class ApiTest(unittest.TestCase):
def try_del(self, server_port, querystr):
self.resource_del_called = 0
opener = urllib2.build_opener(urllib2.HTTPHandler)
request = urllib2.Request(self.get_url('/api/resource/1', server_port, querystr))
opener = build_opener(HTTPHandler)
request = Request(self.get_url('/api/resource/1', server_port, querystr))
request.get_method = lambda: 'DEL'
f = opener.open(request)
@ -127,8 +138,8 @@ class ApiTest(unittest.TestCase):
# GET: By default we don't serve any files if we just define an API
exception_thrown = False
try:
urllib2.urlopen(self.get_url('/', server_port, None))
except urllib2.HTTPError as e:
urlopen(self.get_url('/', server_port, None))
except HTTPError as e:
self.assertEqual(e.code, 404)
exception_thrown = True
self.assertTrue(exception_thrown)
@ -143,8 +154,8 @@ class ApiTest(unittest.TestCase):
# GET: Return 404 for non-existent endpoint
exception_thrown = False
try:
urllib2.urlopen(self.get_url('/api/resource/', server_port, None))
except urllib2.HTTPError as e:
urlopen(self.get_url('/api/resource/', server_port, None))
except HTTPError as e:
self.assertEqual(e.code, 404)
exception_thrown = True
self.assertTrue(exception_thrown)
@ -152,9 +163,11 @@ class ApiTest(unittest.TestCase):
# POST: POST should also return 404
exception_thrown = False
try:
urllib2.urlopen(self.get_url('/api/resource/', server_port, None),
data=json.dumps({}))
except urllib2.HTTPError as e:
urlopen(
self.get_url('/api/resource/', server_port, None),
data=json.dumps({}),
)
except HTTPError as e:
self.assertEqual(e.code, 404)
exception_thrown = True
self.assertTrue(exception_thrown)
@ -162,12 +175,11 @@ class ApiTest(unittest.TestCase):
# DEL: DEL should also return 404
exception_thrown = False
try:
opener = urllib2.build_opener(urllib2.HTTPHandler)
request = urllib2.Request(self.get_url('/api/resource/', server_port,
None))
opener = build_opener(HTTPHandler)
request = Request(self.get_url('/api/resource/', server_port, None))
request.get_method = lambda: 'DEL'
opener.open(request)
except urllib2.HTTPError:
except HTTPError:
self.assertEqual(e.code, 404)
exception_thrown = True
self.assertTrue(exception_thrown)
@ -181,7 +193,7 @@ class ApiTest(unittest.TestCase):
server_port = httpd.httpd.server_port
# We defined a docroot, so we expect a directory listing
f = urllib2.urlopen(self.get_url('/', server_port, None))
f = urlopen(self.get_url('/', server_port, None))
try:
self.assertEqual(f.getcode(), 200)
except AttributeError:
@ -197,7 +209,7 @@ class ProxyTest(unittest.TestCase):
def tearDown(self):
# reset proxy opener in case it changed
urllib2.install_opener(None)
install_opener(None)
def test_proxy(self):
docroot = tempfile.mkdtemp()
@ -211,7 +223,7 @@ class ProxyTest(unittest.TestCase):
def index_contents(host): return '%s index' % host
index = file(os.path.join(docroot, index_filename), 'w')
index = open(os.path.join(docroot, index_filename), 'w')
index.write(index_contents('*'))
index.close()
@ -219,12 +231,13 @@ class ProxyTest(unittest.TestCase):
httpd.start(block=False)
server_port = httpd.httpd.server_port
proxy_support = urllib2.ProxyHandler({'http': 'http://127.0.0.1:%d' %
server_port})
urllib2.install_opener(urllib2.build_opener(proxy_support))
proxy_support = ProxyHandler({
'http': 'http://127.0.0.1:%d' % server_port,
})
install_opener(build_opener(proxy_support))
for host in hosts:
f = urllib2.urlopen(url(host))
f = urlopen(url(host))
try:
self.assertEqual(f.getcode(), 200)
except AttributeError:
@ -239,18 +252,19 @@ class ProxyTest(unittest.TestCase):
httpd.start(block=False)
server_port = httpd.httpd.server_port
proxy_support = urllib2.ProxyHandler({'http': 'http://127.0.0.1:%d' %
server_port})
urllib2.install_opener(urllib2.build_opener(proxy_support))
proxy_support = ProxyHandler({
'http': 'http://127.0.0.1:%d' % server_port,
})
install_opener(build_opener(proxy_support))
# set up dirs
for host in hosts:
os.mkdir(os.path.join(docroot, host))
file(os.path.join(docroot, host, index_filename), 'w') \
open(os.path.join(docroot, host, index_filename), 'w') \
.write(index_contents(host))
for host in hosts:
f = urllib2.urlopen(url(host))
f = urlopen(url(host))
try:
self.assertEqual(f.getcode(), 200)
except AttributeError:
@ -259,8 +273,8 @@ class ProxyTest(unittest.TestCase):
exc = None
try:
urllib2.urlopen(url(unproxied_host))
except urllib2.HTTPError as e:
urlopen(url(unproxied_host))
except HTTPError as e:
exc = e
self.assertNotEqual(exc, None)
self.assertEqual(exc.code, 404)

View File

@ -1,9 +1,10 @@
[DEFAULT]
subsuite = mozbase, os == "linux"
skip-if = python == 3
[api.py]
skip-if = python == 3
[baseurl.py]
[basic.py]
[filelisting.py]
skip-if = python == 3
[paths.py]
[requestlog.py]

View File

@ -9,7 +9,9 @@ from mozfile import TemporaryDirectory
import mozhttpd
import os
import unittest
import urllib2
from six.moves.urllib.request import urlopen
from six.moves.urllib.error import HTTPError
import mozunit
@ -17,13 +19,13 @@ import mozunit
class PathTest(unittest.TestCase):
def try_get(self, url, expected_contents):
f = urllib2.urlopen(url)
f = urlopen(url)
self.assertEqual(f.getcode(), 200)
self.assertEqual(f.read(), expected_contents)
def try_get_expect_404(self, url):
with self.assertRaises(urllib2.HTTPError) as cm:
urllib2.urlopen(url)
with self.assertRaises(HTTPError) as cm:
urlopen(url)
self.assertEqual(404, cm.exception.code)
def test_basic(self):
@ -36,8 +38,8 @@ class PathTest(unittest.TestCase):
path_mappings={'/files': d2}
)
httpd.start(block=False)
self.try_get(httpd.get_url("/test1.txt"), "test 1 contents")
self.try_get(httpd.get_url("/files/test2.txt"), "test 2 contents")
self.try_get(httpd.get_url("/test1.txt"), b"test 1 contents")
self.try_get(httpd.get_url("/files/test2.txt"), b"test 2 contents")
self.try_get_expect_404(httpd.get_url("/files/test2_nope.txt"))
httpd.stop()
@ -51,8 +53,8 @@ class PathTest(unittest.TestCase):
'/abc': d2, }
)
httpd.start(block=False)
self.try_get(httpd.get_url("/abcxyz/test1.txt"), "test 1 contents")
self.try_get(httpd.get_url("/abc/test2.txt"), "test 2 contents")
self.try_get(httpd.get_url("/abcxyz/test1.txt"), b"test 1 contents")
self.try_get(httpd.get_url("/abc/test2.txt"), b"test 2 contents")
httpd.stop()
def test_multipart_path_mapping(self):
@ -63,7 +65,7 @@ class PathTest(unittest.TestCase):
path_mappings={'/abc/def/ghi': d1}
)
httpd.start(block=False)
self.try_get(httpd.get_url("/abc/def/ghi/test1.txt"), "test 1 contents")
self.try_get(httpd.get_url("/abc/def/ghi/test1.txt"), b"test 1 contents")
self.try_get_expect_404(httpd.get_url("/abc/test1.txt"))
self.try_get_expect_404(httpd.get_url("/abc/def/test1.txt"))
httpd.stop()

View File

@ -5,10 +5,11 @@
from __future__ import absolute_import
import mozhttpd
import urllib2
import os
import unittest
from six.moves.urllib.request import urlopen
import mozunit
here = os.path.dirname(os.path.abspath(__file__))
@ -21,7 +22,7 @@ class RequestLogTest(unittest.TestCase):
httpd = mozhttpd.MozHttpd(port=0, docroot=here, log_requests=log_requests)
httpd.start(block=False)
url = "http://%s:%s/" % ('127.0.0.1', httpd.httpd.server_port)
f = urllib2.urlopen(url)
f = urlopen(url)
f.read()
return httpd.request_log

View File

@ -135091,6 +135091,18 @@
{}
]
],
"css/css-text/overflow-wrap/overflow-wrap-min-content-size-002.html": [
[
"/css/css-text/overflow-wrap/overflow-wrap-min-content-size-002.html",
[
[
"/css/css-text/overflow-wrap/reference/overflow-wrap-min-content-size-002-ref.html",
"=="
]
],
{}
]
],
"css/css-text/overflow-wrap/word-wrap-001.html": [
[
"/css/css-text/overflow-wrap/word-wrap-001.html",
@ -257609,6 +257621,11 @@
{}
]
],
"css/css-text/overflow-wrap/reference/overflow-wrap-min-content-size-002-ref.html": [
[
{}
]
],
"css/css-text/support/1x1-green.png": [
[
{}
@ -534201,6 +534218,10 @@
"5858dbb88a775bb8975f338d866b6fc837485364",
"reftest"
],
"css/css-text/overflow-wrap/overflow-wrap-min-content-size-002.html": [
"ae7abc617493b9e2c9313215a3f38b77c37d9450",
"reftest"
],
"css/css-text/overflow-wrap/reference/overflow-wrap-break-word-001-ref.html": [
"0b16a0bdb25ddd647ad96dd82e3430274667ee87",
"support"
@ -534221,6 +534242,10 @@
"f3e09183b565f71e38158cc5cd4d96ab5fbf25d4",
"support"
],
"css/css-text/overflow-wrap/reference/overflow-wrap-min-content-size-002-ref.html": [
"3686ae6a0e278a970b861c165f0f840df302db70",
"support"
],
"css/css-text/overflow-wrap/word-wrap-001.html": [
"8bafc4d48bbfee1e6c465a95b29792ba33c30346",
"reftest"

View File

@ -1,3 +1,3 @@
[mix-blend-mode-blended-element-overflow-hidden-and-border-radius.html]
expected:
if os == "linux": FAIL
if os == "linux" and not webrender: FAIL

View File

@ -1,3 +1,3 @@
[mix-blend-mode-intermediate-element-overflow-hidden-and-border-radius.html]
expected:
if os == "linux": FAIL
if os == "linux" and not webrender: FAIL

View File

@ -1,3 +1,3 @@
[mix-blend-mode-parent-with-border-radius.html]
expected:
if os == "linux": FAIL
if os == "linux" and not webrender: FAIL

View File

@ -1,2 +0,0 @@
[overflow-wrap-min-content-size-001.html]
expected: FAIL

View File

@ -164,12 +164,6 @@
[MediaStreamTrackAudioSourceNode interface: existence and properties of interface prototype object's @@unscopables property]
expected: FAIL
[PeriodicWave must be primary interface of new PeriodicWave(context)]
expected: FAIL
[Stringification of new PeriodicWave(context)]
expected: FAIL
[AudioWorklet interface: existence and properties of interface object]
expected: FAIL

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Text Test: overflow-wrap: break-word and intrinsic sizing</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
<link rel="author" title="Mozilla" href="https://www.mozilla.org/">
<link rel="help" href="https://drafts.csswg.org/css-text-3/#overflow-wrap-property">
<meta name="flags" content="">
<link rel="match" href="reference/overflow-wrap-min-content-size-002-ref.html">
<meta name="assert" content="overflow-wrap:break-word doesn't break grapheme cluster and min-content intrinsic size should take that into account.">
<style>
#wrapper {
width: 0px;
overflow-wrap: break-word;
}
#test {
float: left;
border: 2px solid blue;
}
</style>
<p>Test passes if the glyphs are completely inside the blue box.
<div id="wrapper">
<div id="test">&#x0ba8;&#x0bbf;&#x0bbf;&#x0bbf;&#x0bbf;&#x0ba8;&#x0bbf;&#x0bbf;&#x0bbf;&#x0bbf;</div>
</div>

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Text Test reference</title>
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
<link rel="author" title="Mozilla" href="https://www.mozilla.org/">
<style>
#test {
float: left;
border: 2px solid blue;
}
</style>
<p>Test passes if the glyphs are completely inside the blue box.
<div id="wrapper">
<div id="test">&#x0ba8;&#x0bbf;&#x0bbf;&#x0bbf;&#x0bbf;<br>&#x0ba8;&#x0bbf;&#x0bbf;&#x0bbf;&#x0bbf;</div>
</div>

Some files were not shown because too many files have changed in this diff Show More