diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js
index 8bfb31fed831..4528cb72b5a6 100644
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1330,9 +1330,11 @@ pref("devtools.tilt.outro_transition", true);
// 'Open Recent'-menu.
// - showTrailingSpace: Whether to highlight trailing space or not.
// - enableCodeFolding: Whether to enable code folding or not.
+// - enableAutocompletion: Whether to enable JavaScript autocompletion.
pref("devtools.scratchpad.recentFilesMax", 10);
pref("devtools.scratchpad.showTrailingSpace", false);
pref("devtools.scratchpad.enableCodeFolding", true);
+pref("devtools.scratchpad.enableAutocompletion", true);
// Enable the Style Editor.
pref("devtools.styleeditor.enabled", true);
diff --git a/browser/base/content/browser-fullScreen.js b/browser/base/content/browser-fullScreen.js
index 4e416ef0437e..15bfbf7f9158 100644
--- a/browser/base/content/browser-fullScreen.js
+++ b/browser/base/content/browser-fullScreen.js
@@ -566,6 +566,8 @@ var FullScreen = {
navbar.appendChild(fullscreenctls);
}
fullscreenctls.hidden = aShow;
+
+ ToolbarIconColor.inferFromText();
}
};
XPCOMUtils.defineLazyGetter(FullScreen, "useLionFullScreen", function() {
diff --git a/browser/base/content/browser-social.js b/browser/base/content/browser-social.js
index 47e4a4219d0f..e3ec47491e5d 100644
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -69,7 +69,7 @@ SocialUI = {
Services.prefs.addObserver("social.toast-notifications.enabled", this, false);
gBrowser.addEventListener("ActivateSocialFeature", this._activationEventHandler.bind(this), true, true);
- document.getElementById("PanelUI-popup").addEventListener("popupshown", SocialMarks.updatePanelButtons, true);
+ PanelUI.panel.addEventListener("popupshown", SocialUI.updateState, true);
// menupopups that list social providers. we only populate them when shown,
// and if it has not been done already.
@@ -102,7 +102,7 @@ SocialUI = {
Services.prefs.removeObserver("social.toast-notifications.enabled", this);
- document.getElementById("PanelUI-popup").removeEventListener("popupshown", SocialMarks.updatePanelButtons, true);
+ PanelUI.panel.removeEventListener("popupshown", SocialUI.updateState, true);
document.getElementById("viewSidebarMenu").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
document.getElementById("social-statusarea-popup").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
@@ -288,7 +288,7 @@ SocialUI = {
// called on tab/urlbar/location changes and after customization. Update
// anything that is tab specific.
updateState: function() {
- if (!this.enabled)
+ if (!SocialUI.enabled)
return;
SocialMarks.update();
SocialShare.update();
@@ -432,6 +432,12 @@ SocialFlyout = {
}
SocialShare = {
+ // Share panel may be attached to the overflow or menu button depending on
+ // customization, we need to manage open state of the anchor.
+ get anchor() {
+ let widget = CustomizableUI.getWidget("social-share-button");
+ return widget.forWindow(window).anchor;
+ },
get panel() {
return document.getElementById("social-share-panel");
},
@@ -523,7 +529,15 @@ SocialShare = {
},
get shareButton() {
- return document.getElementById("social-share-button");
+ // web-panels (bookmark/sidebar) don't include customizableui, so
+ // nsContextMenu fails when accessing shareButton, breaking
+ // browser_bug409481.js.
+ if (!window.CustomizableUI)
+ return null;
+ let widget = CustomizableUI.getWidget("social-share-button");
+ if (!widget || !widget.areaType)
+ return null;
+ return widget.forWindow(window).node;
},
canSharePage: function(aURI) {
@@ -540,23 +554,29 @@ SocialShare = {
let shareButton = this.shareButton;
shareButton.hidden = !SocialUI.enabled ||
[p for (p of Social.providers) if (p.shareURL)].length == 0;
- shareButton.disabled = shareButton.hidden || !this.canSharePage(gBrowser.currentURI);
+ let disabled = shareButton.hidden || !this.canSharePage(gBrowser.currentURI);
- // also update the relevent command's disabled state so the keyboard
+ // 1. update the relevent command's disabled state so the keyboard
// shortcut only works when available.
+ // 2. If the button has been relocated to a place that is not visible by
+ // default (e.g. menu panel) then the disabled attribute will not update
+ // correctly based on the command, so we update the attribute directly as.
let cmd = document.getElementById("Social:SharePage");
- if (shareButton.disabled)
+ if (disabled) {
cmd.setAttribute("disabled", "true");
- else
+ shareButton.setAttribute("disabled", "true");
+ } else {
cmd.removeAttribute("disabled");
+ shareButton.removeAttribute("disabled");
+ }
},
onShowing: function() {
- this.shareButton.setAttribute("open", "true");
+ this.anchor.setAttribute("open", "true");
},
onHidden: function() {
- this.shareButton.removeAttribute("open");
+ this.anchor.removeAttribute("open");
this.iframe.setAttribute("src", "data:text/plain;charset=utf8,");
this.currentShare = null;
},
@@ -670,8 +690,7 @@ SocialShare = {
iframe.setAttribute("origin", provider.origin);
iframe.setAttribute("src", shareEndpoint);
- let navBar = document.getElementById("nav-bar");
- let anchor = document.getAnonymousElementByAttribute(this.shareButton, "class", "toolbarbutton-icon");
+ let anchor = document.getAnonymousElementByAttribute(this.anchor, "class", "toolbarbutton-icon");
this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
Social.setErrorListener(iframe, this.setErrorMessage.bind(this));
}
@@ -1357,7 +1376,6 @@ SocialMarks = {
// querySelectorAll does not work on the menu panel the panel, so we have to
// do this the hard way.
let providers = SocialMarks.getProviders();
- let panel = document.getElementById("PanelUI-popup");
for (let p of providers) {
let widgetId = SocialMarks._toolbarHelper.idFromOrigin(p.origin);
let widget = CustomizableUI.getWidget(widgetId);
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 743995d28b1b..7a39a302ae2c 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -947,6 +947,8 @@ var gBrowserInit = {
}
#endif
+ ToolbarIconColor.init();
+
// Wait until chrome is painted before executing code not critical to making the window visible
this._boundDelayedStartup = this._delayedStartup.bind(this, mustLoadSidebar);
window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
@@ -1283,6 +1285,8 @@ var gBrowserInit = {
TabsInTitlebar.uninit();
+ ToolbarIconColor.uninit();
+
var enumerator = Services.wm.getEnumerator(null);
enumerator.getNext();
if (!enumerator.hasMoreElements()) {
@@ -4296,6 +4300,8 @@ function setToolbarVisibility(toolbar, isVisible, persist=true) {
PlacesToolbarHelper.init();
BookmarkingUI.onToolbarVisibilityChange();
gBrowser.updateWindowResizers();
+ if (isVisible)
+ ToolbarIconColor.inferFromText();
}
var TabsInTitlebar = {
@@ -4540,6 +4546,8 @@ var TabsInTitlebar = {
titlebar.style.marginBottom = "";
menubar.style.paddingBottom = "";
}
+
+ ToolbarIconColor.inferFromText();
},
_sizePlaceholder: function (type, width) {
@@ -7192,3 +7200,71 @@ function BrowserOpenNewTabOrWindow(event) {
BrowserOpenTab();
}
}
+
+let ToolbarIconColor = {
+ init: function () {
+ this._initialized = true;
+
+ window.addEventListener("activate", this);
+ window.addEventListener("deactivate", this);
+ Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
+
+ // If the window isn't active now, we assume that it has never been active
+ // before and will soon become active such that inferFromText will be
+ // called from the initial activate event.
+ if (Services.focus.activeWindow == window)
+ this.inferFromText();
+ },
+
+ uninit: function () {
+ this._initialized = false;
+
+ window.removeEventListener("activate", this);
+ window.removeEventListener("deactivate", this);
+ Services.obs.removeObserver(this, "lightweight-theme-styling-update");
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "activate":
+ case "deactivate":
+ this.inferFromText();
+ break;
+ }
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "lightweight-theme-styling-update":
+ // inferFromText needs to run after LightweightThemeConsumer.jsm's
+ // lightweight-theme-styling-update observer.
+ setTimeout(() => { this.inferFromText(); }, 0);
+ break;
+ }
+ },
+
+ inferFromText: function () {
+ if (!this._initialized)
+ return;
+
+ function parseRGB(aColorString) {
+ let rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
+ rgb.shift();
+ return rgb.map(x => parseInt(x));
+ }
+
+ let toolbarSelector = "#navigator-toolbox > toolbar:not([collapsed=true]):not(#addon-bar)";
+#ifdef XP_MACOSX
+ toolbarSelector += ":not([type=menubar])";
+#endif
+
+ for (let toolbar of document.querySelectorAll(toolbarSelector)) {
+ let [r, g, b] = parseRGB(getComputedStyle(toolbar).color);
+ let luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
+ if (luminance <= 110)
+ toolbar.removeAttribute("brighttext");
+ else
+ toolbar.setAttribute("brighttext", "true");
+ }
+ }
+}
diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul
index 1a9bf7fadc65..2d48525e27f0 100644
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -899,10 +899,10 @@
diff --git a/browser/base/content/content.js b/browser/base/content/content.js
index 1a1f36066f8b..ea84540e881d 100644
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -10,8 +10,6 @@ Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
"resource:///modules/ContentLinkHandler.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
- "resource:///modules/translation/LanguageDetector.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
"resource://gre/modules/LoginManagerContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
@@ -442,51 +440,9 @@ let PageStyleHandler = {
};
PageStyleHandler.init();
-let TranslationHandler = {
- init: function() {
- let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebProgress);
- webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
- },
-
- /* nsIWebProgressListener implementation */
- onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
- // Don't bother if we're not a toplevel document, if this isn't the 'stop'
- // notification, or if the content document has gone away
- if (!aWebProgress.isTopLevel ||
- !(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) ||
- !content)
- return;
-
- let url = aRequest.name;
- if (!url.startsWith("http://") && !url.startsWith("https://"))
- return;
-
- // Grab a 60k sample of text from the page.
- let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
- .createInstance(Ci.nsIDocumentEncoder);
- encoder.init(content.document, "text/plain", encoder.SkipInvisibleContent);
- let string = encoder.encodeToStringWithMaxLength(60 * 1024);
-
- // Language detection isn't reliable on very short strings.
- if (string.length < 100)
- return;
-
- LanguageDetector.detectLanguage(string).then(result => {
- if (result.confident)
- sendAsyncMessage("LanguageDetection:Result", result.language);
- });
- },
-
- // Unused methods.
- onProgressChange: function() {},
- onLocationChange: function() {},
- onStatusChange: function() {},
- onSecurityChange: function() {},
-
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
- Ci.nsISupportsWeakReference])
-};
-
-if (Services.prefs.getBoolPref("browser.translation.detectLanguage"))
- TranslationHandler.init();
+// Keep a reference to the translation content handler to avoid it it being GC'ed.
+let trHandler = null;
+if (Services.prefs.getBoolPref("browser.translation.detectLanguage")) {
+ Cu.import("resource:///modules/translation/TranslationContentHandler.jsm");
+ trHandler = new TranslationContentHandler(global, docShell);
+}
diff --git a/browser/components/sessionstore/src/SessionStore.jsm b/browser/components/sessionstore/src/SessionStore.jsm
index f1ac8f7a2e76..71045083bd47 100644
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -3414,7 +3414,7 @@ let SessionStoreInternal = {
* with respect to |browser|.
*/
isCurrentEpoch: function (browser, epoch) {
- return this._browserEpochs.get(browser.permanentKey, 0) == epoch;
+ return (this._browserEpochs.get(browser.permanentKey) || 0) == epoch;
},
};
diff --git a/browser/components/translation/BingTranslator.jsm b/browser/components/translation/BingTranslator.jsm
new file mode 100644
index 000000000000..774e88140e86
--- /dev/null
+++ b/browser/components/translation/BingTranslator.jsm
@@ -0,0 +1,370 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+this.EXPORTED_SYMBOLS = [ "BingTranslation" ];
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-common/rest.js");
+
+// The maximum amount of net data allowed per request on Bing's API.
+const MAX_REQUEST_DATA = 5000; // Documentation says 10000 but anywhere
+ // close to that is refused by the service.
+
+// The maximum number of chunks allowed to be translated in a single
+// request.
+const MAX_REQUEST_CHUNKS = 1000; // Documentation says 2000.
+
+// Self-imposed limit of 15 requests. This means that a page that would need
+// to be broken in more than 15 requests won't be fully translated.
+// The maximum amount of data that we will translate for a single page
+// is MAX_REQUESTS * MAX_REQUEST_DATA.
+const MAX_REQUESTS = 15;
+
+/**
+ * Translates a webpage using Bing's Translation API.
+ *
+ * @param translationDocument The TranslationDocument object that represents
+ * the webpage to be translated
+ * @param sourceLanguage The source language of the document
+ * @param targetLanguage The target language for the translation
+ *
+ * @returns {Promise} A promise that will resolve when the translation
+ * task is finished.
+ */
+this.BingTranslation = function(translationDocument, sourceLanguage, targetLanguage) {
+ this.translationDocument = translationDocument;
+ this.sourceLanguage = sourceLanguage;
+ this.targetLanguage = targetLanguage;
+ this._pendingRequests = 0;
+ this._partialSuccess = false;
+};
+
+this.BingTranslation.prototype = {
+ /**
+ * Performs the translation, splitting the document into several chunks
+ * respecting the data limits of the API.
+ *
+ * @returns {Promise} A promise that will resolve when the translation
+ * task is finished.
+ */
+ translate: function() {
+ return Task.spawn(function *() {
+ let currentIndex = 0;
+ this._onFinishedDeferred = Promise.defer();
+
+ // Let's split the document into various requests to be sent to
+ // Bing's Translation API.
+ for (let requestCount = 0; requestCount < MAX_REQUESTS; requestCount++) {
+ // Generating the text for each request can be expensive, so
+ // let's take the opportunity of the chunkification process to
+ // allow for the event loop to attend other pending events
+ // before we continue.
+ yield CommonUtils.laterTickResolvingPromise();
+
+ // Determine the data for the next request.
+ let request = this._generateNextTranslationRequest(currentIndex);
+
+ // Create a real request to the server, and put it on the
+ // pending requests list.
+ let bingRequest = new BingRequest(request.data,
+ this.sourceLanguage,
+ this.targetLanguage);
+ this._pendingRequests++;
+ bingRequest.fireRequest().then(this._chunkCompleted.bind(this));
+
+ currentIndex = request.lastIndex;
+ if (request.finished) {
+ break;
+ }
+ }
+
+ return this._onFinishedDeferred.promise;
+ }.bind(this));
+ },
+
+ /**
+ * Function called when a request sent to the server is completed.
+ * This function handles determining if the response was successful or not,
+ * calling the function to parse the result, and resolving the promise
+ * returned by the public `translate()` method when all chunks are completed.
+ *
+ * @param request The BingRequest sent to the server.
+ */
+ _chunkCompleted: function(bingRequest) {
+ this._pendingRequests--;
+ if (bingRequest.requestSucceeded &&
+ this._parseChunkResult(bingRequest)) {
+ // error on request
+ this._partialSuccess = true;
+ }
+
+ // Check if all pending requests have been
+ // completed and then resolves the promise.
+ // If at least one chunk was successful, the
+ // promise will be resolved positively which will
+ // display the "Success" state for the infobar. Otherwise,
+ // the "Error" state will appear.
+ if (this._pendingRequests == 0) {
+ if (this._partialSuccess) {
+ this._onFinishedDeferred.resolve("success");
+ } else {
+ this._onFinishedDeferred.reject("failure");
+ }
+ }
+ },
+
+ /**
+ * This function parses the result returned by Bing's Http.svc API,
+ * which is a XML file that contains a number of elements. To our
+ * particular interest, the only part of the response that matters
+ * are the nodes, which contains the resulting
+ * items that were sent to be translated.
+ *
+ * @param request The request sent to the server.
+ * @returns boolean True if parsing of this chunk was successful.
+ */
+ _parseChunkResult: function(bingRequest) {
+ let domParser = Cc["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Ci.nsIDOMParser);
+
+ let results;
+ try {
+ let doc = domParser.parseFromString(bingRequest.networkRequest
+ .response.body, "text/xml");
+ results = doc.querySelectorAll("TranslatedText");
+ } catch (e) {
+ return false;
+ }
+
+ let len = results.length;
+ if (len != bingRequest.translationData.length) {
+ // This should never happen, but if the service returns a different number
+ // of items (from the number of items submitted), we can't use this chunk
+ // because all items would be paired incorrectly.
+ return false;
+ }
+
+ let error = false;
+ for (let i = 0; i < len; i++) {
+ try {
+ bingRequest.translationData[i][0].parseResult(
+ results[i].firstChild.nodeValue
+ );
+ } catch (e) { error = true; }
+ }
+
+ return !error;
+ },
+
+ /**
+ * This function will determine what is the data to be used for
+ * the Nth request we are generating, based on the input params.
+ *
+ * @param startIndex What is the index, in the roots list, that the
+ * chunk should start.
+ */
+ _generateNextTranslationRequest: function(startIndex) {
+ let currentDataSize = 0;
+ let currentChunks = 0;
+ let output = [];
+ let rootsList = this.translationDocument.roots;
+
+ for (let i = startIndex; i < rootsList.length; i++) {
+ let root = rootsList[i];
+ let text = this.translationDocument.generateTextForItem(root);
+ if (!text) {
+ continue;
+ }
+
+ text = escapeXML(text);
+ let newCurSize = currentDataSize + text.length;
+ let newChunks = currentChunks + 1;
+
+ if (newCurSize > MAX_REQUEST_DATA ||
+ newChunks > MAX_REQUEST_CHUNKS) {
+
+ // If we've reached the API limits, let's stop accumulating data
+ // for this request and return. We return information useful for
+ // the caller to pass back on the next call, so that the function
+ // can keep working from where it stopped.
+ return {
+ data: output,
+ finished: false,
+ lastIndex: i
+ };
+ }
+
+ currentDataSize = newCurSize;
+ currentChunks = newChunks;
+ output.push([root, text]);
+ }
+
+ return {
+ data: output,
+ finished: true,
+ lastIndex: 0
+ };
+ }
+};
+
+/**
+ * Represents a request (for 1 chunk) sent off to Bing's service.
+ *
+ * @params translationData The data to be used for this translation,
+ * generated by the generateNextTranslationRequest...
+ * function.
+ * @param sourceLanguage The source language of the document.
+ * @param targetLanguage The target language for the translation.
+ *
+ */
+function BingRequest(translationData, sourceLanguage, targetLanguage) {
+ this.translationData = translationData;
+ this.sourceLanguage = sourceLanguage;
+ this.targetLanguage = targetLanguage;
+}
+
+BingRequest.prototype = {
+ /**
+ * Initiates the request
+ */
+ fireRequest: function() {
+ return Task.spawn(function *(){
+ let token = yield BingTokenManager.getToken();
+ let auth = "Bearer " + token;
+ let request = new RESTRequest("https://api.microsofttranslator.com/v2/Http.svc/TranslateArray");
+ request.setHeader("Content-type", "text/xml");
+ request.setHeader("Authorization", auth);
+
+ let requestString =
+ '' +
+ '' +
+ '' + this.sourceLanguage + '' +
+ '' +
+ 'text/html' +
+ '' +
+ '' +
+ '';
+
+ for (let [, text] of this.translationData) {
+ requestString += '' + text + '';
+ }
+
+ requestString += '' +
+ '' + this.targetLanguage + '' +
+ '';
+
+ let utf8 = CommonUtils.encodeUTF8(requestString);
+
+ let deferred = Promise.defer();
+ request.post(utf8, function(err) {
+ deferred.resolve(this);
+ }.bind(this));
+
+ this.networkRequest = request;
+ return deferred.promise;
+ }.bind(this));
+ },
+
+ /**
+ * Checks if the request succeeded. Only valid
+ * after the request has finished.
+ *
+ * @returns True if the request succeeded.
+ */
+ get requestSucceeded() {
+ return !this.networkRequest.error &&
+ this.networkRequest.response.success;
+ }
+};
+
+/**
+ * Authentication Token manager for the API
+ */
+let BingTokenManager = {
+ _currentToken: null,
+ _currentExpiryTime: 0,
+ _pendingRequest: null,
+
+ /**
+ * Get a valid, non-expired token to be used for the API calls.
+ *
+ * @returns {Promise} A promise that resolves with the token
+ * string once it is obtained. The token returned
+ * can be the same one used in the past if it is still
+ * valid.
+ */
+ getToken: function() {
+ if (this._pendingRequest) {
+ return this._pendingRequest;
+ }
+
+ let remainingMs = this._currentExpiryTime - new Date();
+ // Our existing token is still good for more than a minute, let's use it.
+ if (remainingMs > 60 * 1000) {
+ return Promise.resolve(this._currentToken);
+ }
+
+ return this._getNewToken();
+ },
+
+ /**
+ * Generates a new token from the server.
+ *
+ * @returns {Promise} A promise that resolves with the token
+ * string once it is obtained.
+ */
+ _getNewToken: function() {
+ let request = new RESTRequest("https://datamarket.accesscontrol.windows.net/v2/OAuth2-13");
+ request.setHeader("Content-type", "application/x-www-form-urlencoded");
+ let params = [
+ "grant_type=client_credentials",
+ "scope=" + encodeURIComponent("http://api.microsofttranslator.com"),
+ "client_id=",
+ "client_secret="
+ ];
+
+ let deferred = Promise.defer();
+ this._pendingRequest = deferred.promise;
+ request.post(params.join("&"), function(err) {
+ this._pendingRequest = null;
+
+ if (err) {
+ deferred.reject(err);
+ }
+
+ try {
+ let json = JSON.parse(this.response.body);
+ let token = json.access_token;
+ let expires_in = json.expires_in;
+ BingTokenManager._currentToken = token;
+ BingTokenManager._currentExpiryTime = new Date(Date.now() + expires_in * 1000);
+ deferred.resolve(token);
+ } catch (e) {
+ deferred.reject(e);
+ }
+ });
+
+ return deferred.promise;
+ }
+};
+
+/**
+ * Escape a string to be valid XML content.
+ */
+function escapeXML(aStr) {
+ return aStr.toString()
+ .replace("&", "&", "g")
+ .replace('"', """, "g")
+ .replace("'", "'", "g")
+ .replace("<", "<", "g")
+ .replace(">", ">", "g");
+}
diff --git a/browser/components/translation/Translation.jsm b/browser/components/translation/Translation.jsm
index c74ab61ea6c9..34867c5826e9 100644
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -56,6 +56,7 @@ this.Translation = {
*/
function TranslationUI(aBrowser) {
this.browser = aBrowser;
+ aBrowser.messageManager.addMessageListener("Translation:Finished", this);
}
TranslationUI.prototype = {
@@ -68,6 +69,11 @@ TranslationUI.prototype = {
this.state = this.STATE_TRANSLATING;
this.translatedFrom = aFrom;
this.translatedTo = aTo;
+
+ this.browser.messageManager.sendAsyncMessage(
+ "Translation:TranslateDocument",
+ { from: aFrom, to: aTo }
+ );
},
showURLBarIcon: function(aTranslated) {
@@ -109,11 +115,13 @@ TranslationUI.prototype = {
showOriginalContent: function() {
this.showURLBarIcon();
this.originalShown = true;
+ this.browser.messageManager.sendAsyncMessage("Translation:ShowOriginal");
},
showTranslatedContent: function() {
this.showURLBarIcon(true);
this.originalShown = false;
+ this.browser.messageManager.sendAsyncMessage("Translation:ShowTranslation");
},
get notificationBox() this.browser.ownerGlobal.gBrowser.getNotificationBox(),
@@ -153,5 +161,19 @@ TranslationUI.prototype = {
return null;
return this.showTranslationInfoBar();
+ },
+
+ receiveMessage: function(msg) {
+ switch (msg.name) {
+ case "Translation:Finished":
+ if (msg.data.success) {
+ this.state = this.STATE_TRANSLATED;
+ this.showURLBarIcon(true);
+ this.originalShown = false;
+ } else {
+ this.state = this.STATE_ERROR;
+ }
+ break;
+ }
}
};
diff --git a/browser/components/translation/TranslationContentHandler.jsm b/browser/components/translation/TranslationContentHandler.jsm
new file mode 100644
index 000000000000..efbb3d87ada0
--- /dev/null
+++ b/browser/components/translation/TranslationContentHandler.jsm
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [ "TranslationContentHandler" ];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
+ "resource:///modules/translation/LanguageDetector.jsm");
+
+this.TranslationContentHandler = function(global, docShell) {
+ let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+
+ global.addMessageListener("Translation:TranslateDocument", this);
+ global.addMessageListener("Translation:ShowTranslation", this);
+ global.addMessageListener("Translation:ShowOriginal", this);
+ this.global = global;
+}
+
+TranslationContentHandler.prototype = {
+ /* nsIWebProgressListener implementation */
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (!aWebProgress.isTopLevel ||
+ !(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) ||
+ !this.global.content)
+ return;
+
+ let url = aRequest.name;
+ if (!url.startsWith("http://") && !url.startsWith("https://"))
+ return;
+
+ // Grab a 60k sample of text from the page.
+ let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
+ .createInstance(Ci.nsIDocumentEncoder);
+ encoder.init(this.global.content.document, "text/plain", encoder.SkipInvisibleContent);
+ let string = encoder.encodeToStringWithMaxLength(60 * 1024);
+
+ // Language detection isn't reliable on very short strings.
+ if (string.length < 100)
+ return;
+
+ LanguageDetector.detectLanguage(string).then(result => {
+ if (result.confident)
+ this.global.sendAsyncMessage("LanguageDetection:Result", result.language);
+ });
+ },
+
+ // Unused methods.
+ onProgressChange: function() {},
+ onLocationChange: function() {},
+ onStatusChange: function() {},
+ onSecurityChange: function() {},
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference]),
+
+ receiveMessage: function(msg) {
+ switch (msg.name) {
+ case "Translation:TranslateDocument":
+ {
+ Cu.import("resource:///modules/translation/TranslationDocument.jsm");
+ Cu.import("resource:///modules/translation/BingTranslator.jsm");
+
+ let translationDocument = new TranslationDocument(this.global.content.document);
+ let bingTranslation = new BingTranslation(translationDocument,
+ msg.data.from,
+ msg.data.to);
+
+ this.global.content.translationDocument = translationDocument;
+ bingTranslation.translate().then(
+ success => {
+ this.global.sendAsyncMessage("Translation:Finished", {success: true});
+ translationDocument.showTranslation();
+ },
+ error => {
+ this.global.sendAsyncMessage("Translation:Finished", {success: false});
+ }
+ );
+ break;
+ }
+
+ case "Translation:ShowOriginal":
+ this.global.content.translationDocument.showOriginal();
+ break;
+
+ case "Translation:ShowTranslation":
+ this.global.content.translationDocument.showTranslation();
+ break;
+ }
+ }
+};
diff --git a/browser/components/translation/TranslationDocument.jsm b/browser/components/translation/TranslationDocument.jsm
new file mode 100644
index 000000000000..02fb3c68051d
--- /dev/null
+++ b/browser/components/translation/TranslationDocument.jsm
@@ -0,0 +1,440 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+this.EXPORTED_SYMBOLS = [ "TranslationDocument" ];
+
+const SHOW_ELEMENT = Ci.nsIDOMNodeFilter.SHOW_ELEMENT;
+const SHOW_TEXT = Ci.nsIDOMNodeFilter.SHOW_TEXT;
+const TEXT_NODE = Ci.nsIDOMNode.TEXT_NODE;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://gre/modules/Task.jsm");
+
+/**
+ * This class represents a document that is being translated,
+ * and it is responsible for parsing the document,
+ * generating the data structures translation (the list of
+ * translation items and roots), and managing the original
+ * and translated texts on the translation items.
+ *
+ * @param document The document to be translated
+ */
+this.TranslationDocument = function(document) {
+ this.itemsMap = new Map();
+ this.roots = [];
+ this._init(document);
+};
+
+this.TranslationDocument.prototype = {
+ /**
+ * Initializes the object and populates
+ * the roots lists.
+ *
+ * @param document The document to be translated
+ */
+ _init: function(document) {
+ let window = document.defaultView;
+ let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ // Get all the translation nodes in the document's body:
+ // a translation node is a node from the document which
+ // contains useful content for translation, and therefore
+ // must be included in the translation process.
+ let nodeList = winUtils.getTranslationNodes(document.body);
+
+ let length = nodeList.length;
+
+ for (let i = 0; i < length; i++) {
+ let node = nodeList.item(i);
+ let isRoot = nodeList.isTranslationRootAtIndex(i);
+
+ // Create a TranslationItem object for this node.
+ // This function will also add it to the this.roots array.
+ this._createItemForNode(node, i, isRoot);
+ }
+
+ // At first all roots are stored in the roots list, and only after
+ // the process has finished we're able to determine which roots are
+ // simple, and which ones are not.
+
+ // A simple root is defined by a root with no children items, which
+ // basically represents an element from a page with only text content
+ // inside.
+
+ // This distinction is useful for optimization purposes: we treat a
+ // simple root as plain-text in the translation process and with that
+ // we are able to reduce their data payload sent to the translation service.
+
+ for (let root of this.roots) {
+ if (root.children.length == 0 &&
+ root.nodeRef.childElementCount == 0) {
+ root.isSimpleRoot = true;
+ }
+ }
+ },
+
+ /**
+ * Creates a TranslationItem object, which should be called
+ * for each node returned by getTranslationNodes.
+ *
+ * @param node The DOM node for this item.
+ * @param id A unique, numeric id for this item.
+ * @parem isRoot A boolean saying whether this item is a root.
+ *
+ * @returns A TranslationItem object.
+ */
+ _createItemForNode: function(node, id, isRoot) {
+ if (this.itemsMap.has(node)) {
+ return this.itemsMap.get(node);
+ }
+
+ let item = new TranslationItem(node, id, isRoot);
+
+ if (isRoot) {
+ // Root items do not have a parent item.
+ this.roots.push(item);
+ } else {
+ let parentItem = this.itemsMap.get(node.parentNode);
+ if (parentItem) {
+ parentItem.children.push(item);
+ }
+ }
+
+ this.itemsMap.set(node, item);
+ return item;
+ },
+
+ /**
+ * Generate the text string that represents a TranslationItem object.
+ * Besides generating the string, it's also stored in the "original"
+ * field of the TranslationItem object, which needs to be stored for
+ * later to be used in the "Show Original" functionality.
+ *
+ * @param item A TranslationItem object
+ *
+ * @returns A string representation of the TranslationItem.
+ */
+ generateTextForItem: function(item) {
+ if (item.isSimpleRoot) {
+ let text = item.nodeRef.firstChild.nodeValue.trim();
+ item.original = [text];
+ return text;
+ }
+
+ let localName = item.isRoot ? "div" : "b";
+ let str = '<' + localName + ' id="n' + item.id + '">';
+
+ item.original = [];
+
+ for (let child of item.nodeRef.childNodes) {
+ if (child.nodeType == TEXT_NODE) {
+ let x = child.nodeValue.trim();
+ str += x;
+ item.original.push(x);
+ continue;
+ }
+
+ let objInMap = this.itemsMap.get(child);
+ if (objInMap) {
+ // If this childNode is present in the itemsMap, it means
+ // it's a translation node: it has useful content for translation.
+ // In this case, we need to stringify this node.
+ item.original.push(objInMap);
+ str += this.generateTextForItem(objInMap);
+ } else {
+ // Otherwise, if this node doesn't contain any useful content,
+ // we can simply replace it by a placeholder node.
+ // We can't simply eliminate this node from our string representation
+ // because that could change the HTML structure (e.g., it would
+ // probably merge two separate text nodes).
+ str += ' ';
+ }
+ }
+
+ str += '' + localName + '>';
+ return str;
+ },
+
+ /**
+ * Changes the document to display its translated
+ * content.
+ */
+ showTranslation: function() {
+ this._swapDocumentContent("translation");
+ },
+
+ /**
+ * Changes the document to display its original
+ * content.
+ */
+ showOriginal: function() {
+ this._swapDocumentContent("original");
+ },
+
+ /**
+ * Swap the document with the resulting translation,
+ * or back with the original content.
+ *
+ * @param target A string that is either "translation"
+ * or "original".
+ */
+ _swapDocumentContent: function(target) {
+ Task.spawn(function *() {
+ // Let the event loop breath on every 100 nodes
+ // that are replaced.
+ const YIELD_INTERVAL = 100;
+ let count = YIELD_INTERVAL;
+
+ for (let root of this.roots) {
+ root.swapText(target);
+ if (count-- == 0) {
+ count = YIELD_INTERVAL;
+ yield CommonUtils.laterTickResolvingPromise();
+ }
+ }
+ }.bind(this));
+ }
+};
+
+/**
+ * This class represents an item for translation. It's basically our
+ * wrapper class around a node returned by getTranslationNode, with
+ * more data and structural information on it.
+ *
+ * At the end of the translation process, besides the properties below,
+ * a TranslationItem will contain two other properties: one called "original"
+ * and one called "translation". They are twin objects, one which reflect
+ * the structure of that node in its original state, and the other in its
+ * translated state.
+ *
+ * The "original" array is generated in the generateTextForItem function,
+ * and the "translation" array is generated when the translation results
+ * are parsed.
+ *
+ * They are both arrays, which contain a mix of strings and references to
+ * child TranslationItems. The references in both arrays point to the * same *
+ * TranslationItem object, but they might appear in different orders between the
+ * "original" and "translation" arrays.
+ *
+ * An example:
+ *
+ * English:
Welcome to Mozilla's website
+ * Portuguese:
Bem vindo a pagina da Mozilla
+ *
+ * TranslationItem n1 = {
+ * id: 1,
+ * original: ["Welcome to", ptr to n2, "website"]
+ * translation: ["Bem vindo a pagina", ptr to n2]
+ * }
+ *
+ * TranslationItem n2 = {
+ * id: 2,
+ * original: ["Mozilla's"],
+ * translation: ["da Mozilla"]
+ * }
+ */
+function TranslationItem(node, id, isRoot) {
+ this.nodeRef = node;
+ this.id = id;
+ this.isRoot = isRoot;
+ this.children = [];
+}
+
+TranslationItem.prototype = {
+ isRoot: false,
+ isSimpleRoot: false,
+
+ toString: function() {
+ let rootType = this._isRoot
+ ? (this._isSimpleRoot ? ' (simple root)' : ' (non simple root)')
+ : '';
+ return "[object TranslationItem: <" + this.nodeRef.localName + ">"
+ + rootType + "]";
+ },
+
+ /**
+ * This function will parse the result of the translation of one translation
+ * item. If this item was a simple root, all we sent was a plain-text version
+ * of it, so the result is also straightforward text.
+ *
+ * For non-simple roots, we sent a simplified HTML representation of that
+ * node, and we'll first parse that into an HTML doc and then call the
+ * parseResultNode helper function to parse it.
+ *
+ * While parsing, the result is stored in the "translation" field of the
+ * TranslationItem, which will be used to display the final translation when
+ * all items are finished. It remains stored too to allow back-and-forth
+ * switching between the "Show Original" and "Show Translation" functions.
+ *
+ * @param result A string with the textual result received from the server,
+ * which can be plain-text or a serialized HTML doc.
+ */
+ parseResult: function(result) {
+ if (this.isSimpleRoot) {
+ this.translation = [result];
+ return;
+ }
+
+ let domParser = Cc["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Ci.nsIDOMParser);
+
+ let doc = domParser.parseFromString(result, "text/html");
+ parseResultNode(this, doc.body.firstChild);
+ },
+
+ /**
+ * This function finds a child TranslationItem
+ * with the given id.
+ * @param id The id to look for, in the format "n#"
+ * @returns A TranslationItem with the given id, or null if
+ * it was not found.
+ */
+ getChildById: function(id) {
+ for (let child of this.children) {
+ if (("n" + child.id) == id) {
+ return child;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Swap the text of this TranslationItem between
+ * its original and translated states.
+ *
+ * @param target A string that is either "translation"
+ * or "original".
+ */
+ swapText: function(target) {
+ swapTextForItem(this, target);
+ }
+};
+
+/**
+ * Helper function to parse a HTML doc result.
+ * How it works:
+ *
+ * An example result string is:
+ *
+ *
Hello World of Mozilla.
+ *
+ * For an element node, we look at its id and find the corresponding
+ * TranslationItem that was associated with this node, and then we
+ * walk down it repeating the process.
+ *
+ * For text nodes we simply add it as a string.
+ */
+function parseResultNode(item, node) {
+ item.translation = [];
+ for (let child of node.childNodes) {
+ if (child.nodeType == TEXT_NODE) {
+ item.translation.push(child.nodeValue);
+ } else {
+ let translationItemChild = item.getChildById(child.id);
+
+ if (translationItemChild) {
+ item.translation.push(translationItemChild);
+ parseResultNode(translationItemChild, child);
+ }
+ }
+ }
+}
+
+/**
+ * Helper function to swap the text of a TranslationItem
+ * between its original and translated states.
+ *
+ * @param item A TranslationItem object
+ * @param target A string that is either "translation"
+ * or "original".
+ */
+function swapTextForItem(item, target) {
+ // visitStack is the stack of items that we still need to visit.
+ // Let's start the process by adding the root item.
+ let visitStack = [ item ];
+ let source = target == "translation" ? "original" : "translation";
+
+ while (visitStack.length > 0) {
+ let curItem = visitStack.shift();
+
+ let domNode = curItem.nodeRef;
+ if (!domNode) {
+ // Skipping this item due to a missing node.
+ continue;
+ }
+
+ let sourceNodeCount = 0;
+
+ if (!curItem[target]) {
+ // Translation not found for this item. This could be due to
+ // an error in the server response. For example, if a translation
+ // was broken in various chunks, and one of the chunks failed,
+ // the items from that chunk will be missing its "translation"
+ // field.
+ continue;
+ }
+
+ // Now let's walk through all items in the `target` array of the
+ // TranslationItem. This means either the TranslationItem.original or
+ // TranslationItem.translation array.
+ for (let child of curItem[target]) {
+ // If the array element is another TranslationItem object, let's
+ // add it to the stack to be visited
+ if (child instanceof TranslationItem) {
+ // Adding this child to the stack.
+ visitStack.push(child);
+ continue;
+ }
+
+ // If it's a string, say, the Nth string of the `target` array, let's
+ // replace the Nth child TextNode of this element with this string.
+ // During our translation process we skipped all empty text nodes, so we
+ // must also skip them here. If there are not enough text nodes to be used,
+ // a new text node will be created and appended to the end of the element.
+ let targetTextNode = getNthNonEmptyTextNodeFromElement(sourceNodeCount++, domNode);
+
+ // A trailing and a leading space must be preserved because they are meaningful in HTML.
+ let preSpace = targetTextNode.nodeValue.startsWith(" ") ? " " : "";
+ let endSpace = targetTextNode.nodeValue.endsWith(" ") ? " " : "";
+ targetTextNode.nodeValue = preSpace + child + endSpace;
+ }
+
+ // The translated version of a node might have less text nodes than its original
+ // version. If that's the case, let's clear the remaining nodes.
+ if (sourceNodeCount > 0) {
+ clearRemainingNonEmptyTextNodesFromElement(sourceNodeCount, domNode);
+ }
+ }
+}
+
+function getNthNonEmptyTextNodeFromElement(n, element) {
+ for (let childNode of element.childNodes) {
+ if (childNode.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
+ childNode.nodeValue.trim() != "") {
+ if (n-- == 0)
+ return childNode;
+ }
+ }
+
+ // If there are not enough DOM nodes, let's create a new one.
+ return element.appendChild(element.ownerDocument.createTextNode(""));
+}
+
+function clearRemainingNonEmptyTextNodesFromElement(start, element) {
+ let count = 0;
+ for (let childNode of element.childNodes) {
+ if (childNode.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
+ childNode.nodeValue.trim() != "") {
+ if (count++ >= start) {
+ childNode.nodeValue = "";
+ }
+ }
+ }
+}
diff --git a/browser/components/translation/moz.build b/browser/components/translation/moz.build
index f393a1a8660d..68b87bb91b9e 100644
--- a/browser/components/translation/moz.build
+++ b/browser/components/translation/moz.build
@@ -5,10 +5,13 @@
JS_MODULES_PATH = 'modules/translation'
EXTRA_JS_MODULES = [
+ 'BingTranslator.jsm',
'cld2/cld-worker.js',
'cld2/cld-worker.js.mem',
'LanguageDetector.jsm',
- 'Translation.jsm'
+ 'Translation.jsm',
+ 'TranslationContentHandler.jsm',
+ 'TranslationDocument.jsm'
]
JAR_MANIFESTS += ['jar.mn']
diff --git a/browser/devtools/jar.mn b/browser/devtools/jar.mn
index 8a25aa4a77be..4b9be6538dbc 100644
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -53,6 +53,8 @@ browser.jar:
content/browser/devtools/codemirror/comment-fold.js (sourceeditor/codemirror/fold/comment-fold.js)
content/browser/devtools/codemirror/xml-fold.js (sourceeditor/codemirror/fold/xml-fold.js)
content/browser/devtools/codemirror/foldgutter.js (sourceeditor/codemirror/fold/foldgutter.js)
+ content/browser/devtools/codemirror/tern.js (sourceeditor/codemirror/tern.js)
+ content/browser/devtools/codemirror/show-hint.js (sourceeditor/codemirror/show-hint.js)
content/browser/devtools/codemirror/mozilla.css (sourceeditor/codemirror/mozilla.css)
content/browser/devtools/debugger.xul (debugger/debugger.xul)
content/browser/devtools/debugger.css (debugger/debugger.css)
diff --git a/browser/devtools/scratchpad/scratchpad.js b/browser/devtools/scratchpad/scratchpad.js
index bee432609fc3..5868a451ada3 100644
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -35,6 +35,7 @@ const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax";
const SHOW_TRAILING_SPACE = "devtools.scratchpad.showTrailingSpace";
const ENABLE_CODE_FOLDING = "devtools.scratchpad.enableCodeFolding";
+const ENABLE_AUTOCOMPLETION = "devtools.scratchpad.enableAutocompletion";
const VARIABLES_VIEW_URL = "chrome://browser/content/devtools/widgets/VariablesView.xul";
@@ -1605,15 +1606,17 @@ var Scratchpad = {
mode: Editor.modes.js,
value: initialText,
lineNumbers: true,
+ contextMenu: "scratchpad-text-popup",
showTrailingSpace: Services.prefs.getBoolPref(SHOW_TRAILING_SPACE),
enableCodeFolding: Services.prefs.getBoolPref(ENABLE_CODE_FOLDING),
- contextMenu: "scratchpad-text-popup"
+ autocomplete: Services.prefs.getBoolPref(ENABLE_AUTOCOMPLETION),
};
this.editor = new Editor(config);
this.editor.appendTo(document.querySelector("#scratchpad-editor")).then(() => {
var lines = initialText.split("\n");
+ this.editor.setupAutoCompletion();
this.editor.on("change", this._onChanged);
this.editor.on("save", () => this.saveFile());
this.editor.focus();
@@ -1627,7 +1630,7 @@ var Scratchpad = {
this.populateRecentFilesMenu();
PreferenceObserver.init();
CloseObserver.init();
- }).then(null, (err) => console.log(err.message));
+ }).then(null, (err) => console.error(err));
this._setupCommandListeners();
this._setupPopupShowingListeners();
},
diff --git a/browser/devtools/scratchpad/test/browser.ini b/browser/devtools/scratchpad/test/browser.ini
index be82f6befd59..eabec14df8a6 100644
--- a/browser/devtools/scratchpad/test/browser.ini
+++ b/browser/devtools/scratchpad/test/browser.ini
@@ -3,6 +3,7 @@ skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
subsuite = devtools
support-files = head.js
+[browser_scratchpad_autocomplete.js]
[browser_scratchpad_browser_last_window_closing.js]
[browser_scratchpad_reset_undo.js]
[browser_scratchpad_display_outputs_errors.js]
diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_autocomplete.js b/browser/devtools/scratchpad/test/browser_scratchpad_autocomplete.js
new file mode 100644
index 000000000000..728d5abcbacc
--- /dev/null
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_autocomplete.js
@@ -0,0 +1,66 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* Bug 968896 */
+
+// Test the completions using numbers.
+const source = "0x1.";
+const completions = ["toExponential", "toFixed", "toString"];
+Cu.import("resource://gre/modules/Task.jsm");
+
+function test() {
+ const options = { tabContent: "test scratchpad autocomplete" };
+ openTabAndScratchpad(options)
+ .then(Task.async(runTests))
+ .then(finish, console.error);
+}
+
+
+function* runTests([win, sp]) {
+ const {editor} = sp;
+ const editorWin = editor.container.contentWindow;
+
+ // Show the completions popup.
+ sp.setText(source);
+ sp.editor.setCursor({ line: 0, ch: source.length });
+ yield keyOnce("suggestion-entered", " ", { ctrlKey: true });
+
+ // Get the hints popup container.
+ const hints = editorWin.document.querySelector(".CodeMirror-hints");
+
+ ok(hints,
+ "The hint container should exist.")
+ is(hints.childNodes.length, 3,
+ "The hint container should have the completions.");
+
+ let i = 0;
+ for (let completion of completions) {
+ let active = hints.querySelector(".CodeMirror-hint-active");
+ is(active.textContent, completion,
+ "Check that completion " + i++ + " is what is expected.");
+ yield keyOnce("suggestion-entered", "VK_DOWN");
+ }
+
+ // We should have looped around to the first suggestion again. Accept it.
+ yield keyOnce("after-suggest", "VK_RETURN");
+
+ is(sp.getText(), source + completions[0],
+ "Autocompletion should work and select the right element.");
+
+ // Check that the information tooltips work.
+ sp.setText("5");
+ yield keyOnce("show-information", " ", { shiftKey: true });
+
+ // Get the information container.
+ const info = editorWin.document.querySelector(".CodeMirror-Tern-information");
+ ok(info,
+ "Info tooltip should appear.");
+ is(info.textContent.slice(0, 6), "number",
+ "Info tooltip should have expected contents.");
+
+ function keyOnce(event, key, opts = {}) {
+ const p = editor.once(event);
+ EventUtils.synthesizeKey(key, opts, editorWin);
+ return p;
+ }
+}
diff --git a/browser/devtools/scratchpad/test/head.js b/browser/devtools/scratchpad/test/head.js
index 56a3954cfcac..c39d59215422 100644
--- a/browser/devtools/scratchpad/test/head.js
+++ b/browser/devtools/scratchpad/test/head.js
@@ -7,6 +7,8 @@
const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+const {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
+const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
let gScratchpadWindow; // Reference to the Scratchpad chrome window object
@@ -36,10 +38,8 @@ SimpleTest.registerCleanupFunction(() => {
* gScratchpadWindow global is also updated to reference the new window
* object.
*/
-function openScratchpad(aReadyCallback, aOptions)
+function openScratchpad(aReadyCallback, aOptions = {})
{
- aOptions = aOptions || {};
-
let win = aOptions.window ||
Scratchpad.ScratchpadManager.openScratchpad(aOptions.state);
if (!win) {
@@ -70,6 +70,30 @@ function openScratchpad(aReadyCallback, aOptions)
return gScratchpadWindow;
}
+/**
+ * Open a new tab and then open a scratchpad.
+ * @param object aOptions
+ * Optional. Options for opening the tab and scratchpad. In addition
+ * to the options supported by openScratchpad, the following options
+ * are supported:
+ * - tabContent
+ * A string providing the html content of the tab.
+ * @return Promise
+ */
+function openTabAndScratchpad(aOptions = {})
+{
+ waitForExplicitFinish();
+ return new promise(resolve => {
+ gBrowser.selectedTab = gBrowser.addTab();
+ let {selectedBrowser} = gBrowser;
+ selectedBrowser.addEventListener("load", function onLoad() {
+ selectedBrowser.removeEventListener("load", onLoad, true);
+ openScratchpad((win, sp) => resolve([win, sp]), aOptions);
+ }, true);
+ content.location = "data:text/html;charset=utf8," + (aOptions.tabContent || "");
+ });
+}
+
/**
* Create a temporary file, write to it and call a callback
* when done.
@@ -180,7 +204,6 @@ function runAsyncCallbackTests(aScratchpad, aTests)
return deferred.promise;
}
-
function cleanup()
{
if (gScratchpadWindow) {
diff --git a/browser/devtools/sourceeditor/autocomplete.js b/browser/devtools/sourceeditor/autocomplete.js
index 0e68ed978e7b..a28e56876580 100644
--- a/browser/devtools/sourceeditor/autocomplete.js
+++ b/browser/devtools/sourceeditor/autocomplete.js
@@ -6,20 +6,62 @@
const cssAutoCompleter = require("devtools/sourceeditor/css-autocompleter");
const { AutocompletePopup } = require("devtools/shared/autocomplete-popup");
+const CM_TERN_SCRIPTS = [
+ "chrome://browser/content/devtools/codemirror/tern.js",
+ "chrome://browser/content/devtools/codemirror/show-hint.js"
+];
+
const privates = new WeakMap();
/**
- * Prepares an editor instance for autocompletion, setting up the popup and the
- * CSS completer instance.
+ * Prepares an editor instance for autocompletion.
*/
-function setupAutoCompletion(ctx, walker) {
+function setupAutoCompletion(ctx, options) {
let { cm, ed, Editor } = ctx;
let win = ed.container.contentWindow.wrappedJSObject;
+ let {CodeMirror} = win;
let completer = null;
- if (ed.config.mode == Editor.modes.css)
- completer = new cssAutoCompleter({walker: walker});
+ let autocompleteKey = "Ctrl-" +
+ Editor.keyFor("autocompletion", { noaccel: true });
+ if (ed.config.mode == Editor.modes.js) {
+ let defs = [
+ "tern/browser",
+ "tern/ecma5",
+ ].map(require);
+
+ CM_TERN_SCRIPTS.forEach(ed.loadScript, ed);
+ win.tern = require("tern/tern");
+ cm.tern = new CodeMirror.TernServer({ defs: defs });
+ cm.on("cursorActivity", (cm) => {
+ cm.tern.updateArgHints(cm);
+ });
+
+ let keyMap = {};
+
+ keyMap[autocompleteKey] = (cm) => {
+ cm.tern.getHint(cm, (data) => {
+ CodeMirror.on(data, "shown", () => ed.emit("before-suggest"));
+ CodeMirror.on(data, "close", () => ed.emit("after-suggest"));
+ CodeMirror.on(data, "select", () => ed.emit("suggestion-entered"));
+ CodeMirror.showHint(cm, (cm, cb) => cb(data), { async: true });
+ });
+ };
+
+ keyMap[Editor.keyFor("showInformation", { noaccel: true })] = (cm) => {
+ cm.tern.showType(cm, null, () => {
+ ed.emit("show-information");
+ });
+ };
+
+ cm.addKeyMap(keyMap);
+
+ // TODO: Integrate tern autocompletion with this autocomplete API.
+ return;
+ } else if (ed.config.mode == Editor.modes.css) {
+ completer = new cssAutoCompleter({walker: options.walker});
+ }
let popup = new AutocompletePopup(win.parent.document, {
position: "after_start",
@@ -34,14 +76,14 @@ function setupAutoCompletion(ctx, walker) {
return;
}
- return win.CodeMirror.Pass;
+ return CodeMirror.Pass;
};
let keyMap = {
"Tab": cycle,
"Down": cycle,
- "Shift-Tab": cycle.bind(this, true),
- "Up": cycle.bind(this, true),
+ "Shift-Tab": cycle.bind(null, true),
+ "Up": cycle.bind(null, true),
"Enter": () => {
if (popup && popup.isOpen) {
if (!privates.get(ed).suggestionInsertedOnce) {
@@ -56,10 +98,10 @@ function setupAutoCompletion(ctx, walker) {
return;
}
- return win.CodeMirror.Pass;
+ return CodeMirror.Pass;
}
};
- keyMap[Editor.accel("Space")] = cm => autoComplete(ctx);
+ keyMap[autocompleteKey] = cm => autoComplete(ctx);
cm.addKeyMap(keyMap);
cm.on("keydown", (cm, e) => onEditorKeypress(ctx, e));
diff --git a/browser/devtools/sourceeditor/codemirror/mozilla.css b/browser/devtools/sourceeditor/codemirror/mozilla.css
index 155e6d0154fe..c2691bdf1f44 100644
--- a/browser/devtools/sourceeditor/codemirror/mozilla.css
+++ b/browser/devtools/sourceeditor/codemirror/mozilla.css
@@ -134,4 +134,101 @@ selector in floating-scrollbar-light.css across all platforms. */
.CodeMirror-foldgutter-folded:after {
font-size: 120%;
content: "\25B8";
-}
\ No newline at end of file
+}
+
+.CodeMirror-hints {
+ position: absolute;
+ z-index: 10;
+ overflow: hidden;
+ list-style: none;
+ margin: 0;
+ padding: 2px;
+ border-radius: 3px;
+ font-size: 90%;
+ max-height: 20em;
+ overflow-y: auto;
+}
+
+.CodeMirror-hint {
+ margin: 0;
+ padding: 0 4px;
+ border-radius: 2px;
+ max-width: 19em;
+ overflow: hidden;
+ white-space: pre;
+ cursor: pointer;
+}
+
+.CodeMirror-Tern-completion {
+ -moz-padding-start: 22px;
+ position: relative;
+ line-height: 18px;
+}
+
+.CodeMirror-Tern-completion:before {
+ position: absolute;
+ left: 2px;
+ bottom: 2px;
+ border-radius: 50%;
+ font-size: 12px;
+ font-weight: bold;
+ height: 15px;
+ width: 15px;
+ line-height: 16px;
+ text-align: center;
+ color: #ffffff;
+ box-sizing: border-box;
+}
+
+.CodeMirror-Tern-completion-unknown:before {
+ content: "?";
+}
+
+.CodeMirror-Tern-completion-object:before {
+ content: "O";
+}
+
+.CodeMirror-Tern-completion-fn:before {
+ content: "F";
+}
+
+.CodeMirror-Tern-completion-array:before {
+ content: "A";
+}
+
+.CodeMirror-Tern-completion-number:before {
+ content: "N";
+}
+
+.CodeMirror-Tern-completion-string:before {
+ content: "S";
+}
+
+.CodeMirror-Tern-completion-bool:before {
+ content: "B";
+}
+
+.CodeMirror-Tern-completion-guess {
+ color: #999;
+}
+
+.CodeMirror-Tern-tooltip {
+ border-radius: 3px;
+ padding: 2px 5px;
+ white-space: pre-wrap;
+ max-width: 40em;
+ position: absolute;
+ z-index: 10;
+}
+
+.CodeMirror-Tern-hint-doc {
+ max-width: 25em;
+}
+
+.CodeMirror-Tern-farg-current {
+ text-decoration: underline;
+}
+
+.CodeMirror-Tern-fhint-guess {
+ opacity: .7;
+}
diff --git a/browser/devtools/sourceeditor/codemirror/show-hint.js b/browser/devtools/sourceeditor/codemirror/show-hint.js
new file mode 100644
index 000000000000..00aadc575e19
--- /dev/null
+++ b/browser/devtools/sourceeditor/codemirror/show-hint.js
@@ -0,0 +1,341 @@
+(function() {
+ "use strict";
+
+ var HINT_ELEMENT_CLASS = "CodeMirror-hint";
+ var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
+
+ CodeMirror.showHint = function(cm, getHints, options) {
+ // We want a single cursor position.
+ if (cm.somethingSelected()) return;
+ if (getHints == null) {
+ if (options && options.async) return;
+ else getHints = CodeMirror.hint.auto;
+ }
+
+ if (cm.state.completionActive) cm.state.completionActive.close();
+
+ var completion = cm.state.completionActive = new Completion(cm, getHints, options || {});
+ CodeMirror.signal(cm, "startCompletion", cm);
+ if (completion.options.async)
+ getHints(cm, function(hints) { completion.showHints(hints); }, completion.options);
+ else
+ return completion.showHints(getHints(cm, completion.options));
+ };
+
+ function Completion(cm, getHints, options) {
+ this.cm = cm;
+ this.getHints = getHints;
+ this.options = options;
+ this.widget = this.onClose = null;
+ }
+
+ Completion.prototype = {
+ close: function() {
+ if (!this.active()) return;
+ this.cm.state.completionActive = null;
+
+ if (this.widget) this.widget.close();
+ if (this.onClose) this.onClose();
+ CodeMirror.signal(this.cm, "endCompletion", this.cm);
+ },
+
+ active: function() {
+ return this.cm.state.completionActive == this;
+ },
+
+ pick: function(data, i) {
+ var completion = data.list[i];
+ if (completion.hint) completion.hint(this.cm, data, completion);
+ else this.cm.replaceRange(getText(completion), data.from, data.to);
+ CodeMirror.signal(data, "pick", completion);
+ this.close();
+ },
+
+ showHints: function(data) {
+ if (!data || !data.list.length || !this.active()) return this.close();
+
+ if (this.options.completeSingle != false && data.list.length == 1)
+ this.pick(data, 0);
+ else
+ this.showWidget(data);
+ },
+
+ showWidget: function(data) {
+ this.widget = new Widget(this, data);
+ CodeMirror.signal(data, "shown");
+
+ var debounce = 0, completion = this, finished;
+ var closeOn = this.options.closeCharacters || /[\s()\[\]{};:>,]/;
+ var startPos = this.cm.getCursor(), startLen = this.cm.getLine(startPos.line).length;
+
+ var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
+ return setTimeout(fn, 1000/60);
+ };
+ var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
+
+ function done() {
+ if (finished) return;
+ finished = true;
+ completion.close();
+ completion.cm.off("cursorActivity", activity);
+ if (data) CodeMirror.signal(data, "close");
+ }
+
+ function update() {
+ if (finished) return;
+ CodeMirror.signal(data, "update");
+ if (completion.options.async)
+ completion.getHints(completion.cm, finishUpdate, completion.options);
+ else
+ finishUpdate(completion.getHints(completion.cm, completion.options));
+ }
+ function finishUpdate(data_) {
+ data = data_;
+ if (finished) return;
+ if (!data || !data.list.length) return done();
+ completion.widget = new Widget(completion, data);
+ }
+
+ function clearDebounce() {
+ if (debounce) {
+ cancelAnimationFrame(debounce);
+ debounce = 0;
+ }
+ }
+
+ function activity() {
+ clearDebounce();
+ var pos = completion.cm.getCursor(), line = completion.cm.getLine(pos.line);
+ if (pos.line != startPos.line || line.length - pos.ch != startLen - startPos.ch ||
+ pos.ch < startPos.ch || completion.cm.somethingSelected() ||
+ (pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) {
+ completion.close();
+ } else {
+ debounce = requestAnimationFrame(update);
+ if (completion.widget) completion.widget.close();
+ }
+ }
+ this.cm.on("cursorActivity", activity);
+ this.onClose = done;
+ }
+ };
+
+ function getText(completion) {
+ if (typeof completion == "string") return completion;
+ else return completion.text;
+ }
+
+ function buildKeyMap(options, handle) {
+ var baseMap = {
+ Up: function() {handle.moveFocus(-1);},
+ Down: function() {handle.moveFocus(1);},
+ PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
+ PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
+ Home: function() {handle.setFocus(0);},
+ End: function() {handle.setFocus(handle.length - 1);},
+ Enter: handle.pick,
+ Tab: handle.pick,
+ Esc: handle.close
+ };
+ var ourMap = options.customKeys ? {} : baseMap;
+ function addBinding(key, val) {
+ var bound;
+ if (typeof val != "string")
+ bound = function(cm) { return val(cm, handle); };
+ // This mechanism is deprecated
+ else if (baseMap.hasOwnProperty(val))
+ bound = baseMap[val];
+ else
+ bound = val;
+ ourMap[key] = bound;
+ }
+ if (options.customKeys)
+ for (var key in options.customKeys) if (options.customKeys.hasOwnProperty(key))
+ addBinding(key, options.customKeys[key]);
+ if (options.extraKeys)
+ for (var key in options.extraKeys) if (options.extraKeys.hasOwnProperty(key))
+ addBinding(key, options.extraKeys[key]);
+ return ourMap;
+ }
+
+ function getHintElement(hintsElement, el) {
+ while (el && el != hintsElement) {
+ if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
+ el = el.parentNode;
+ }
+ }
+
+ function Widget(completion, data) {
+ this.completion = completion;
+ this.data = data;
+ var widget = this, cm = completion.cm, options = completion.options;
+
+ var hints = this.hints = document.createElement("ul");
+ hints.className = "CodeMirror-hints";
+ this.selectedHint = options.getDefaultSelection ? options.getDefaultSelection(cm,options,data) : 0;
+
+ var completions = data.list;
+ for (var i = 0; i < completions.length; ++i) {
+ var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
+ var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
+ if (cur.className != null) className = cur.className + " " + className;
+ elt.className = className;
+ if (cur.render) cur.render(elt, data, cur);
+ else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
+ elt.hintId = i;
+ }
+
+ var pos = cm.cursorCoords(options.alignWithWord !== false ? data.from : null);
+ var left = pos.left, top = pos.bottom, below = true;
+ hints.style.left = left + "px";
+ hints.style.top = top + "px";
+ // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
+ var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
+ var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
+ (options.container || document.body).appendChild(hints);
+ var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
+ if (overlapY > 0) {
+ var height = box.bottom - box.top, curTop = box.top - (pos.bottom - pos.top);
+ if (curTop - height > 0) { // Fits above cursor
+ hints.style.top = (top = curTop - height) + "px";
+ below = false;
+ } else if (height > winH) {
+ hints.style.height = (winH - 5) + "px";
+ hints.style.top = (top = pos.bottom - box.top) + "px";
+ var cursor = cm.getCursor();
+ if (data.from.ch != cursor.ch) {
+ pos = cm.cursorCoords(cursor);
+ hints.style.left = (left = pos.left) + "px";
+ box = hints.getBoundingClientRect();
+ }
+ }
+ }
+ var overlapX = box.left - winW;
+ if (overlapX > 0) {
+ if (box.right - box.left > winW) {
+ hints.style.width = (winW - 5) + "px";
+ overlapX -= (box.right - box.left) - winW;
+ }
+ hints.style.left = (left = pos.left - overlapX) + "px";
+ }
+
+ cm.addKeyMap(this.keyMap = buildKeyMap(options, {
+ moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
+ setFocus: function(n) { widget.changeActive(n); },
+ menuSize: function() { return widget.screenAmount(); },
+ length: completions.length,
+ close: function() { completion.close(); },
+ pick: function() { widget.pick(); },
+ data: data
+ }));
+
+ if (options.closeOnUnfocus !== false) {
+ var closingOnBlur;
+ cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
+ cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
+ }
+
+ var startScroll = cm.getScrollInfo();
+ cm.on("scroll", this.onScroll = function() {
+ var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
+ var newTop = top + startScroll.top - curScroll.top;
+ var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
+ if (!below) point += hints.offsetHeight;
+ if (point <= editor.top || point >= editor.bottom) return completion.close();
+ hints.style.top = newTop + "px";
+ hints.style.left = (left + startScroll.left - curScroll.left) + "px";
+ });
+
+ CodeMirror.on(hints, "dblclick", function(e) {
+ var t = getHintElement(hints, e.target || e.srcElement);
+ if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
+ });
+
+ CodeMirror.on(hints, "click", function(e) {
+ var t = getHintElement(hints, e.target || e.srcElement);
+ if (t && t.hintId != null) {
+ widget.changeActive(t.hintId);
+ if (options.completeOnSingleClick) widget.pick();
+ }
+ });
+
+ CodeMirror.on(hints, "mousedown", function() {
+ setTimeout(function(){cm.focus();}, 20);
+ });
+
+ CodeMirror.signal(data, "select", completions[0], hints.firstChild);
+ return true;
+ }
+
+ Widget.prototype = {
+ close: function() {
+ if (this.completion.widget != this) return;
+ this.completion.widget = null;
+ this.hints.parentNode.removeChild(this.hints);
+ this.completion.cm.removeKeyMap(this.keyMap);
+
+ var cm = this.completion.cm;
+ if (this.completion.options.closeOnUnfocus !== false) {
+ cm.off("blur", this.onBlur);
+ cm.off("focus", this.onFocus);
+ }
+ cm.off("scroll", this.onScroll);
+ },
+
+ pick: function() {
+ this.completion.pick(this.data, this.selectedHint);
+ },
+
+ changeActive: function(i, avoidWrap) {
+ if (i >= this.data.list.length)
+ i = avoidWrap ? this.data.list.length - 1 : 0;
+ else if (i < 0)
+ i = avoidWrap ? 0 : this.data.list.length - 1;
+ if (this.selectedHint == i) return;
+ var node = this.hints.childNodes[this.selectedHint];
+ node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
+ node = this.hints.childNodes[this.selectedHint = i];
+ node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
+ if (node.offsetTop < this.hints.scrollTop)
+ this.hints.scrollTop = node.offsetTop - 3;
+ else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
+ this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
+ CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
+ },
+
+ screenAmount: function() {
+ return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
+ }
+ };
+
+ CodeMirror.registerHelper("hint", "auto", function(cm, options) {
+ var helpers = cm.getHelpers(cm.getCursor(), "hint");
+ if (helpers.length) {
+ for (var i = 0; i < helpers.length; i++) {
+ var cur = helpers[i](cm, options);
+ if (cur && cur.list.length) return cur;
+ }
+ } else {
+ var words = cm.getHelper(cm.getCursor(), "hintWords");
+ if (words) return CodeMirror.hint.fromList(cm, {words: words});
+ }
+ });
+
+ CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
+ var cur = cm.getCursor(), token = cm.getTokenAt(cur);
+ var found = [];
+ for (var i = 0; i < options.words.length; i++) {
+ var word = options.words[i];
+ if (word.slice(0, token.string.length) == token.string)
+ found.push(word);
+ }
+
+ if (found.length) return {
+ list: found,
+ from: CodeMirror.Pos(cur.line, token.start),
+ to: CodeMirror.Pos(cur.line, token.end)
+ };
+ });
+
+ CodeMirror.commands.autocomplete = CodeMirror.showHint;
+})();
diff --git a/browser/devtools/sourceeditor/codemirror/tern.js b/browser/devtools/sourceeditor/codemirror/tern.js
new file mode 100644
index 000000000000..5ec498a86cf6
--- /dev/null
+++ b/browser/devtools/sourceeditor/codemirror/tern.js
@@ -0,0 +1,633 @@
+// Glue code between CodeMirror and Tern.
+//
+// Create a CodeMirror.TernServer to wrap an actual Tern server,
+// register open documents (CodeMirror.Doc instances) with it, and
+// call its methods to activate the assisting functions that Tern
+// provides.
+//
+// Options supported (all optional):
+// * defs: An array of JSON definition data structures.
+// * plugins: An object mapping plugin names to configuration
+// options.
+// * getFile: A function(name, c) that can be used to access files in
+// the project that haven't been loaded yet. Simply do c(null) to
+// indicate that a file is not available.
+// * fileFilter: A function(value, docName, doc) that will be applied
+// to documents before passing them on to Tern.
+// * switchToDoc: A function(name) that should, when providing a
+// multi-file view, switch the view or focus to the named file.
+// * showError: A function(editor, message) that can be used to
+// override the way errors are displayed.
+// * completionTip: Customize the content in tooltips for completions.
+// Is passed a single argument—the completion's data as returned by
+// Tern—and may return a string, DOM node, or null to indicate that
+// no tip should be shown. By default the docstring is shown.
+// * typeTip: Like completionTip, but for the tooltips shown for type
+// queries.
+// * responseFilter: A function(doc, query, request, error, data) that
+// will be applied to the Tern responses before treating them
+//
+//
+// It is possible to run the Tern server in a web worker by specifying
+// these additional options:
+// * useWorker: Set to true to enable web worker mode. You'll probably
+// want to feature detect the actual value you use here, for example
+// !!window.Worker.
+// * workerScript: The main script of the worker. Point this to
+// wherever you are hosting worker.js from this directory.
+// * workerDeps: An array of paths pointing (relative to workerScript)
+// to the Acorn and Tern libraries and any Tern plugins you want to
+// load. Or, if you minified those into a single script and included
+// them in the workerScript, simply leave this undefined.
+
+(function() {
+ "use strict";
+ // declare global: tern
+
+ CodeMirror.TernServer = function(options) {
+ var self = this;
+ this.options = options || {};
+ var plugins = this.options.plugins || (this.options.plugins = {});
+ if (!plugins.doc_comment) plugins.doc_comment = true;
+ if (this.options.useWorker) {
+ this.server = new WorkerServer(this);
+ } else {
+ this.server = new tern.Server({
+ getFile: function(name, c) { return getFile(self, name, c); },
+ async: true,
+ defs: this.options.defs || [],
+ plugins: plugins
+ });
+ }
+ this.docs = Object.create(null);
+ this.trackChange = function(doc, change) { trackChange(self, doc, change); };
+
+ this.cachedArgHints = null;
+ this.activeArgHints = null;
+ this.jumpStack = [];
+ };
+
+ CodeMirror.TernServer.prototype = {
+ addDoc: function(name, doc) {
+ var data = {doc: doc, name: name, changed: null};
+ this.server.addFile(name, docValue(this, data));
+ CodeMirror.on(doc, "change", this.trackChange);
+ return this.docs[name] = data;
+ },
+
+ delDoc: function(name) {
+ var found = this.docs[name];
+ if (!found) return;
+ CodeMirror.off(found.doc, "change", this.trackChange);
+ delete this.docs[name];
+ this.server.delFile(name);
+ },
+
+ hideDoc: function(name) {
+ closeArgHints(this);
+ var found = this.docs[name];
+ if (found && found.changed) sendDoc(this, found);
+ },
+
+ complete: function(cm) {
+ var self = this;
+ CodeMirror.showHint(cm, function(cm, c) { return hint(self, cm, c); }, {async: true});
+ },
+
+ getHint: function(cm, c) { return hint(this, cm, c); },
+
+ showType: function(cm, pos, c) { showType(this, cm, pos, c); },
+
+ updateArgHints: function(cm) { updateArgHints(this, cm); },
+
+ jumpToDef: function(cm) { jumpToDef(this, cm); },
+
+ jumpBack: function(cm) { jumpBack(this, cm); },
+
+ rename: function(cm) { rename(this, cm); },
+
+ request: function (cm, query, c, pos) {
+ var self = this;
+ var doc = findDoc(this, cm.getDoc());
+ var request = buildRequest(this, doc, query, pos);
+
+ this.server.request(request, function (error, data) {
+ if (!error && self.options.responseFilter)
+ data = self.options.responseFilter(doc, query, request, error, data);
+ c(error, data);
+ });
+ }
+ };
+
+ var Pos = CodeMirror.Pos;
+ var cls = "CodeMirror-Tern-";
+ var bigDoc = 250;
+
+ function getFile(ts, name, c) {
+ var buf = ts.docs[name];
+ if (buf)
+ c(docValue(ts, buf));
+ else if (ts.options.getFile)
+ ts.options.getFile(name, c);
+ else
+ c(null);
+ }
+
+ function findDoc(ts, doc, name) {
+ for (var n in ts.docs) {
+ var cur = ts.docs[n];
+ if (cur.doc == doc) return cur;
+ }
+ if (!name) for (var i = 0;; ++i) {
+ n = "[doc" + (i || "") + "]";
+ if (!ts.docs[n]) { name = n; break; }
+ }
+ return ts.addDoc(name, doc);
+ }
+
+ function trackChange(ts, doc, change) {
+ var data = findDoc(ts, doc);
+
+ var argHints = ts.cachedArgHints;
+ if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) <= 0)
+ ts.cachedArgHints = null;
+
+ var changed = data.changed;
+ if (changed == null)
+ data.changed = changed = {from: change.from.line, to: change.from.line};
+ var end = change.from.line + (change.text.length - 1);
+ if (change.from.line < changed.to) changed.to = changed.to - (change.to.line - end);
+ if (end >= changed.to) changed.to = end + 1;
+ if (changed.from > change.from.line) changed.from = change.from.line;
+
+ if (doc.lineCount() > bigDoc && change.to - changed.from > 100) setTimeout(function() {
+ if (data.changed && data.changed.to - data.changed.from > 100) sendDoc(ts, data);
+ }, 200);
+ }
+
+ function sendDoc(ts, doc) {
+ ts.server.request({files: [{type: "full", name: doc.name, text: docValue(ts, doc)}]}, function(error) {
+ if (error) console.error(error);
+ else doc.changed = null;
+ });
+ }
+
+ // Completion
+
+ function hint(ts, cm, c) {
+ ts.request(cm, {type: "completions", types: true, docs: true, urls: true}, function(error, data) {
+ if (error) return showError(ts, cm, error);
+ var completions = [], after = "";
+ var from = data.start, to = data.end;
+ if (cm.getRange(Pos(from.line, from.ch - 2), from) == "[\"" &&
+ cm.getRange(to, Pos(to.line, to.ch + 2)) != "\"]")
+ after = "\"]";
+
+ for (var i = 0; i < data.completions.length; ++i) {
+ var completion = data.completions[i], className = typeToIcon(completion.type);
+ if (data.guess) className += " " + cls + "guess";
+ completions.push({text: completion.name + after,
+ displayText: completion.name,
+ className: className,
+ data: completion});
+ }
+
+ var obj = {from: from, to: to, list: completions};
+ var tooltip = null;
+ CodeMirror.on(obj, "close", function() { remove(tooltip); });
+ CodeMirror.on(obj, "update", function() { remove(tooltip); });
+ CodeMirror.on(obj, "select", function(cur, node) {
+ remove(tooltip);
+ var content = ts.options.completionTip ? ts.options.completionTip(cur.data) : cur.data.doc;
+ if (content) {
+ tooltip = makeTooltip(node.parentNode.getBoundingClientRect().right + window.pageXOffset,
+ node.getBoundingClientRect().top + window.pageYOffset, content);
+ tooltip.className += " " + cls + "hint-doc";
+ }
+ });
+ c(obj);
+ });
+ }
+
+ function typeToIcon(type) {
+ var suffix;
+ if (type == "?") suffix = "unknown";
+ else if (type == "number" || type == "string" || type == "bool") suffix = type;
+ else if (/^fn\(/.test(type)) suffix = "fn";
+ else if (/^\[/.test(type)) suffix = "array";
+ else suffix = "object";
+ return cls + "completion " + cls + "completion-" + suffix;
+ }
+
+ // Type queries
+
+ function showType(ts, cm, pos, c) {
+ ts.request(cm, "type", function(error, data) {
+ if (error) return showError(ts, cm, error);
+ if (ts.options.typeTip) {
+ var tip = ts.options.typeTip(data);
+ } else {
+ var tip = elt("span", cls + "information", elt("strong", null, data.type || "not found"));
+ if (data.doc)
+ tip.appendChild(document.createTextNode(" — " + data.doc));
+ if (data.url) {
+ tip.appendChild(document.createTextNode(" "));
+ tip.appendChild(elt("a", null, "[docs]")).href = data.url;
+ }
+ }
+ tempTooltip(cm, tip);
+ c && c(tip);
+ }, pos);
+ }
+
+ // Maintaining argument hints
+
+ function updateArgHints(ts, cm) {
+ closeArgHints(ts);
+
+ if (cm.somethingSelected()) return;
+ var state = cm.getTokenAt(cm.getCursor()).state;
+ var inner = CodeMirror.innerMode(cm.getMode(), state);
+ if (inner.mode.name != "javascript") return;
+ var lex = inner.state.lexical;
+ if (lex.info != "call") return;
+
+ var ch, argPos = lex.pos || 0, tabSize = cm.getOption("tabSize");
+ for (var line = cm.getCursor().line, e = Math.max(0, line - 9), found = false; line >= e; --line) {
+ var str = cm.getLine(line), extra = 0;
+ for (var pos = 0;;) {
+ var tab = str.indexOf("\t", pos);
+ if (tab == -1) break;
+ extra += tabSize - (tab + extra) % tabSize - 1;
+ pos = tab + 1;
+ }
+ ch = lex.column - extra;
+ if (str.charAt(ch) == "(") {found = true; break;}
+ }
+ if (!found) return;
+
+ var start = Pos(line, ch);
+ var cache = ts.cachedArgHints;
+ if (cache && cache.doc == cm.getDoc() && cmpPos(start, cache.start) == 0)
+ return showArgHints(ts, cm, argPos);
+
+ ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) {
+ if (error || !data.type || !(/^fn\(/).test(data.type)) return;
+ ts.cachedArgHints = {
+ start: pos,
+ type: parseFnType(data.type),
+ name: data.exprName || data.name || "fn",
+ guess: data.guess,
+ doc: cm.getDoc()
+ };
+ showArgHints(ts, cm, argPos);
+ });
+ }
+
+ function showArgHints(ts, cm, pos) {
+ closeArgHints(ts);
+
+ var cache = ts.cachedArgHints, tp = cache.type;
+ var tip = elt("span", cache.guess ? cls + "fhint-guess" : null,
+ elt("span", cls + "fname", cache.name), "(");
+ for (var i = 0; i < tp.args.length; ++i) {
+ if (i) tip.appendChild(document.createTextNode(", "));
+ var arg = tp.args[i];
+ tip.appendChild(elt("span", cls + "farg" + (i == pos ? " " + cls + "farg-current" : ""), arg.name || "?"));
+ if (arg.type != "?") {
+ tip.appendChild(document.createTextNode(":\u00a0"));
+ tip.appendChild(elt("span", cls + "type", arg.type));
+ }
+ }
+ tip.appendChild(document.createTextNode(tp.rettype ? ") ->\u00a0" : ")"));
+ if (tp.rettype) tip.appendChild(elt("span", cls + "type", tp.rettype));
+ var place = cm.cursorCoords(null, "page");
+ ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip);
+ }
+
+ function parseFnType(text) {
+ var args = [], pos = 3;
+
+ function skipMatching(upto) {
+ var depth = 0, start = pos;
+ for (;;) {
+ var next = text.charAt(pos);
+ if (upto.test(next) && !depth) return text.slice(start, pos);
+ if (/[{\[\(]/.test(next)) ++depth;
+ else if (/[}\]\)]/.test(next)) --depth;
+ ++pos;
+ }
+ }
+
+ // Parse arguments
+ if (text.charAt(pos) != ")") for (;;) {
+ var name = text.slice(pos).match(/^([^, \(\[\{]+): /);
+ if (name) {
+ pos += name[0].length;
+ name = name[1];
+ }
+ args.push({name: name, type: skipMatching(/[\),]/)});
+ if (text.charAt(pos) == ")") break;
+ pos += 2;
+ }
+
+ var rettype = text.slice(pos).match(/^\) -> (.*)$/);
+
+ return {args: args, rettype: rettype && rettype[1]};
+ }
+
+ // Moving to the definition of something
+
+ function jumpToDef(ts, cm) {
+ function inner(varName) {
+ var req = {type: "definition", variable: varName || null};
+ var doc = findDoc(ts, cm.getDoc());
+ ts.server.request(buildRequest(ts, doc, req), function(error, data) {
+ if (error) return showError(ts, cm, error);
+ if (!data.file && data.url) { window.open(data.url); return; }
+
+ if (data.file) {
+ var localDoc = ts.docs[data.file], found;
+ if (localDoc && (found = findContext(localDoc.doc, data))) {
+ ts.jumpStack.push({file: doc.name,
+ start: cm.getCursor("from"),
+ end: cm.getCursor("to")});
+ moveTo(ts, doc, localDoc, found.start, found.end);
+ return;
+ }
+ }
+ showError(ts, cm, "Could not find a definition.");
+ });
+ }
+
+ if (!atInterestingExpression(cm))
+ dialog(cm, "Jump to variable", function(name) { if (name) inner(name); });
+ else
+ inner();
+ }
+
+ function jumpBack(ts, cm) {
+ var pos = ts.jumpStack.pop(), doc = pos && ts.docs[pos.file];
+ if (!doc) return;
+ moveTo(ts, findDoc(ts, cm.getDoc()), doc, pos.start, pos.end);
+ }
+
+ function moveTo(ts, curDoc, doc, start, end) {
+ doc.doc.setSelection(end, start);
+ if (curDoc != doc && ts.options.switchToDoc) {
+ closeArgHints(ts);
+ ts.options.switchToDoc(doc.name);
+ }
+ }
+
+ // The {line,ch} representation of positions makes this rather awkward.
+ function findContext(doc, data) {
+ var before = data.context.slice(0, data.contextOffset).split("\n");
+ var startLine = data.start.line - (before.length - 1);
+ var start = Pos(startLine, (before.length == 1 ? data.start.ch : doc.getLine(startLine).length) - before[0].length);
+
+ var text = doc.getLine(startLine).slice(start.ch);
+ for (var cur = startLine + 1; cur < doc.lineCount() && text.length < data.context.length; ++cur)
+ text += "\n" + doc.getLine(cur);
+ if (text.slice(0, data.context.length) == data.context) return data;
+
+ var cursor = doc.getSearchCursor(data.context, 0, false);
+ var nearest, nearestDist = Infinity;
+ while (cursor.findNext()) {
+ var from = cursor.from(), dist = Math.abs(from.line - start.line) * 10000;
+ if (!dist) dist = Math.abs(from.ch - start.ch);
+ if (dist < nearestDist) { nearest = from; nearestDist = dist; }
+ }
+ if (!nearest) return null;
+
+ if (before.length == 1)
+ nearest.ch += before[0].length;
+ else
+ nearest = Pos(nearest.line + (before.length - 1), before[before.length - 1].length);
+ if (data.start.line == data.end.line)
+ var end = Pos(nearest.line, nearest.ch + (data.end.ch - data.start.ch));
+ else
+ var end = Pos(nearest.line + (data.end.line - data.start.line), data.end.ch);
+ return {start: nearest, end: end};
+ }
+
+ function atInterestingExpression(cm) {
+ var pos = cm.getCursor("end"), tok = cm.getTokenAt(pos);
+ if (tok.start < pos.ch && (tok.type == "comment" || tok.type == "string")) return false;
+ return /\w/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1));
+ }
+
+ // Variable renaming
+
+ function rename(ts, cm) {
+ var token = cm.getTokenAt(cm.getCursor());
+ if (!/\w/.test(token.string)) showError(ts, cm, "Not at a variable");
+ dialog(cm, "New name for " + token.string, function(newName) {
+ ts.request(cm, {type: "rename", newName: newName, fullDocs: true}, function(error, data) {
+ if (error) return showError(ts, cm, error);
+ applyChanges(ts, data.changes);
+ });
+ });
+ }
+
+ var nextChangeOrig = 0;
+ function applyChanges(ts, changes) {
+ var perFile = Object.create(null);
+ for (var i = 0; i < changes.length; ++i) {
+ var ch = changes[i];
+ (perFile[ch.file] || (perFile[ch.file] = [])).push(ch);
+ }
+ for (var file in perFile) {
+ var known = ts.docs[file], chs = perFile[file];;
+ if (!known) continue;
+ chs.sort(function(a, b) { return cmpPos(b.start, a.start); });
+ var origin = "*rename" + (++nextChangeOrig);
+ for (var i = 0; i < chs.length; ++i) {
+ var ch = chs[i];
+ known.doc.replaceRange(ch.text, ch.start, ch.end, origin);
+ }
+ }
+ }
+
+ // Generic request-building helper
+
+ function buildRequest(ts, doc, query, pos) {
+ var files = [], offsetLines = 0, allowFragments = !query.fullDocs;
+ if (!allowFragments) delete query.fullDocs;
+ if (typeof query == "string") query = {type: query};
+ query.lineCharPositions = true;
+ if (query.end == null) {
+ query.end = pos || doc.doc.getCursor("end");
+ if (doc.doc.somethingSelected())
+ query.start = doc.doc.getCursor("start");
+ }
+ var startPos = query.start || query.end;
+
+ if (doc.changed) {
+ if (doc.doc.lineCount() > bigDoc && allowFragments !== false &&
+ doc.changed.to - doc.changed.from < 100 &&
+ doc.changed.from <= startPos.line && doc.changed.to > query.end.line) {
+ files.push(getFragmentAround(doc, startPos, query.end));
+ query.file = "#0";
+ var offsetLines = files[0].offsetLines;
+ if (query.start != null) query.start = Pos(query.start.line - -offsetLines, query.start.ch);
+ query.end = Pos(query.end.line - offsetLines, query.end.ch);
+ } else {
+ files.push({type: "full",
+ name: doc.name,
+ text: docValue(ts, doc)});
+ query.file = doc.name;
+ doc.changed = null;
+ }
+ } else {
+ query.file = doc.name;
+ }
+ for (var name in ts.docs) {
+ var cur = ts.docs[name];
+ if (cur.changed && cur != doc) {
+ files.push({type: "full", name: cur.name, text: docValue(ts, cur)});
+ cur.changed = null;
+ }
+ }
+
+ return {query: query, files: files};
+ }
+
+ function getFragmentAround(data, start, end) {
+ var doc = data.doc;
+ var minIndent = null, minLine = null, endLine, tabSize = 4;
+ for (var p = start.line - 1, min = Math.max(0, p - 50); p >= min; --p) {
+ var line = doc.getLine(p), fn = line.search(/\bfunction\b/);
+ if (fn < 0) continue;
+ var indent = CodeMirror.countColumn(line, null, tabSize);
+ if (minIndent != null && minIndent <= indent) continue;
+ minIndent = indent;
+ minLine = p;
+ }
+ if (minLine == null) minLine = min;
+ var max = Math.min(doc.lastLine(), end.line + 20);
+ if (minIndent == null || minIndent == CodeMirror.countColumn(doc.getLine(start.line), null, tabSize))
+ endLine = max;
+ else for (endLine = end.line + 1; endLine < max; ++endLine) {
+ var indent = CodeMirror.countColumn(doc.getLine(endLine), null, tabSize);
+ if (indent <= minIndent) break;
+ }
+ var from = Pos(minLine, 0);
+
+ return {type: "part",
+ name: data.name,
+ offsetLines: from.line,
+ text: doc.getRange(from, Pos(endLine, 0))};
+ }
+
+ // Generic utilities
+
+ function cmpPos(a, b) { return a.line - b.line || a.ch - b.ch; }
+
+ function elt(tagname, cls /*, ... elts*/) {
+ var e = document.createElement(tagname);
+ if (cls) e.className = cls;
+ for (var i = 2; i < arguments.length; ++i) {
+ var elt = arguments[i];
+ if (typeof elt == "string") elt = document.createTextNode(elt);
+ e.appendChild(elt);
+ }
+ return e;
+ }
+
+ function dialog(cm, text, f) {
+ if (cm.openDialog)
+ cm.openDialog(text + ": ", f);
+ else
+ f(prompt(text, ""));
+ }
+
+ // Tooltips
+
+ function tempTooltip(cm, content) {
+ var where = cm.cursorCoords();
+ var tip = makeTooltip(where.right + 1, where.bottom, content);
+ function clear() {
+ if (!tip.parentNode) return;
+ cm.off("cursorActivity", clear);
+ fadeOut(tip);
+ }
+ setTimeout(clear, 1700);
+ cm.on("cursorActivity", clear);
+ }
+
+ function makeTooltip(x, y, content) {
+ var node = elt("div", cls + "tooltip", content);
+ node.style.left = x + "px";
+ node.style.top = y + "px";
+ document.body.appendChild(node);
+ return node;
+ }
+
+ function remove(node) {
+ var p = node && node.parentNode;
+ if (p) p.removeChild(node);
+ }
+
+ function fadeOut(tooltip) {
+ tooltip.style.opacity = "0";
+ setTimeout(function() { remove(tooltip); }, 1100);
+ }
+
+ function showError(ts, cm, msg) {
+ if (ts.options.showError)
+ ts.options.showError(cm, msg);
+ else
+ tempTooltip(cm, String(msg));
+ }
+
+ function closeArgHints(ts) {
+ if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; }
+ }
+
+ function docValue(ts, doc) {
+ var val = doc.doc.getValue();
+ if (ts.options.fileFilter) val = ts.options.fileFilter(val, doc.name, doc.doc);
+ return val;
+ }
+
+ // Worker wrapper
+
+ function WorkerServer(ts) {
+ var worker = new Worker(ts.options.workerScript);
+ worker.postMessage({type: "init",
+ defs: ts.options.defs,
+ plugins: ts.options.plugins,
+ scripts: ts.options.workerDeps});
+ var msgId = 0, pending = {};
+
+ function send(data, c) {
+ if (c) {
+ data.id = ++msgId;
+ pending[msgId] = c;
+ }
+ worker.postMessage(data);
+ }
+ worker.onmessage = function(e) {
+ var data = e.data;
+ if (data.type == "getFile") {
+ getFile(ts, data.name, function(err, text) {
+ send({type: "getFile", err: String(err), text: text, id: data.id});
+ });
+ } else if (data.type == "debug") {
+ console.log(data.message);
+ } else if (data.id && pending[data.id]) {
+ pending[data.id](data.err, data.body);
+ delete pending[data.id];
+ }
+ };
+ worker.onerror = function(e) {
+ for (var id in pending) pending[id](e);
+ pending = {};
+ };
+
+ this.addFile = function(name, text) { send({type: "add", name: name, text: text}); };
+ this.delFile = function(name) { send({type: "del", name: name}); };
+ this.request = function(body, c) { send({type: "req", body: body}, c); };
+ }
+})();
diff --git a/browser/devtools/sourceeditor/editor.js b/browser/devtools/sourceeditor/editor.js
index 4fd27c139130..660213434824 100644
--- a/browser/devtools/sourceeditor/editor.js
+++ b/browser/devtools/sourceeditor/editor.js
@@ -150,7 +150,8 @@ function Editor(config) {
styleActiveLine: true,
autoCloseBrackets: "()[]{}''\"\"",
autoCloseEnabled: useAutoClose,
- theme: "mozilla"
+ theme: "mozilla",
+ autocomplete: false
};
// Additional shortcuts.
@@ -336,6 +337,17 @@ Editor.prototype = {
return this.getOption("mode");
},
+ /**
+ * Load a script into editor's containing window.
+ */
+ loadScript: function (url) {
+ if (!this.container) {
+ throw new Error("Can't load a script until the editor is loaded.")
+ }
+ let win = this.container.contentWindow.wrappedJSObject;
+ Services.scriptloader.loadSubScript(url, win, "utf8");
+ },
+
/**
* Changes the value of a currently used highlighting mode.
* See Editor.modes for the list of all suppoert modes.
@@ -822,6 +834,19 @@ Editor.prototype = {
cm.refresh();
},
+ /**
+ * Sets up autocompletion for the editor. Lazily imports the required
+ * dependencies because they vary by editor mode.
+ */
+ setupAutoCompletion: function (options = {}) {
+ if (this.config.autocomplete) {
+ this.extend(require("./autocomplete"));
+ // The autocomplete module will overwrite this.setupAutoCompletion with
+ // a mode specific autocompletion handler.
+ this.setupAutoCompletion(options);
+ }
+ },
+
/**
* Extends an instance of the Editor object with additional
* functions. Each function will be called with context as
diff --git a/browser/devtools/styleeditor/StyleSheetEditor.jsm b/browser/devtools/styleeditor/StyleSheetEditor.jsm
index f18a5a40bd7c..0721e494cb4c 100644
--- a/browser/devtools/styleeditor/StyleSheetEditor.jsm
+++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm
@@ -262,17 +262,16 @@ StyleSheetEditor.prototype = {
readOnly: false,
autoCloseBrackets: "{}()[]",
extraKeys: this._getKeyBindings(),
- contextMenu: "sourceEditorContextMenu"
+ contextMenu: "sourceEditorContextMenu",
+ autocomplete: Services.prefs.getBoolPref(AUTOCOMPLETION_PREF)
};
let sourceEditor = new Editor(config);
sourceEditor.on("dirty-change", this._onPropertyChange);
return sourceEditor.appendTo(inputElement).then(() => {
- if (Services.prefs.getBoolPref(AUTOCOMPLETION_PREF)) {
- sourceEditor.extend(AutoCompleter);
- sourceEditor.setupAutoCompletion(this.walker);
- }
+ sourceEditor.setupAutoCompletion({ walker: this.walker });
+
sourceEditor.on("save", () => {
this.saveToFile();
});
diff --git a/browser/devtools/styleeditor/test/browser_styleeditor_autocomplete.js b/browser/devtools/styleeditor/test/browser_styleeditor_autocomplete.js
index 2624ea6af440..7fd8723de4a7 100644
--- a/browser/devtools/styleeditor/test/browser_styleeditor_autocomplete.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_autocomplete.js
@@ -123,7 +123,7 @@ function testState() {
if (key == 'Ctrl+Space') {
key = " ";
- mods.accelKey = true;
+ mods.ctrlKey = true;
}
else if (key == "VK_RETURN" && entered) {
evt = "popup-hidden";
diff --git a/browser/devtools/webide/content/webide.js b/browser/devtools/webide/content/webide.js
index 51e7d9cfa2aa..d18ecae233cd 100644
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -395,7 +395,6 @@ let UI = {
if (this.toolboxPromise) {
this.toolboxPromise.then(toolbox => {
toolbox.destroy();
- document.querySelector("#action-button-debug").removeAttribute("active");
this.toolboxPromise = null;
}, this.console.error);
}
@@ -436,6 +435,7 @@ let UI = {
let splitter = document.querySelector(".devtools-horizontal-splitter");
splitter.setAttribute("hidden", "true");
+ document.querySelector("#action-button-debug").removeAttribute("active");
},
console: {
diff --git a/browser/devtools/webide/content/webide.xul b/browser/devtools/webide/content/webide.xul
index 052fdf97742a..60f7709bb7b3 100644
--- a/browser/devtools/webide/content/webide.xul
+++ b/browser/devtools/webide/content/webide.xul
@@ -119,7 +119,7 @@
-
+
@@ -132,7 +132,7 @@
-
+
diff --git a/browser/devtools/webide/modules/app-manager.js b/browser/devtools/webide/modules/app-manager.js
index 982387252530..86d7086a5e9b 100644
--- a/browser/devtools/webide/modules/app-manager.js
+++ b/browser/devtools/webide/modules/app-manager.js
@@ -118,6 +118,12 @@ exports.AppManager = AppManager = {
this._runningApps.delete(manifestURL);
this.checkIfProjectIsRunning();
});
+
+ client.addListener("appUninstall", (type, { manifestURL }) => {
+ AppManager.console.log("App uninstall: " + manifestURL);
+ this._runningApps.delete(manifestURL);
+ this.checkIfProjectIsRunning();
+ });
},
_unlistenToApps: function() {
// Is that even possible?
diff --git a/browser/locales/en-US/chrome/browser/devtools/sourceeditor.properties b/browser/locales/en-US/chrome/browser/devtools/sourceeditor.properties
index 170b9ba271b9..40ffe9d1e08e 100644
--- a/browser/locales/en-US/chrome/browser/devtools/sourceeditor.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/sourceeditor.properties
@@ -82,3 +82,11 @@ moveLineUp.commandkey=Alt-Up
# LOCALIZATION NOTE (moveLineDown.commandkey): This is the key to use to move
# the selected lines down.
moveLineDown.commandkey=Alt-Down
+
+# LOCALIZATION NOTE (autocomplete.commandkey): This is the key to use
+# in conjunction with Ctrl for autocompletion.
+autocompletion.commandkey=Space
+
+# LOCALIZATION NOTE (showInformation.commandkey): This is the key to use to
+# show more information, like type inference.
+showInformation.commandkey=Shift-Space
diff --git a/browser/modules/ContentSearch.jsm b/browser/modules/ContentSearch.jsm
index 7cf7d02fffc1..5328be8cbc65 100644
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -10,7 +10,12 @@ this.EXPORTED_SYMBOLS = [
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
const INBOUND_MESSAGE = "ContentSearch";
const OUTBOUND_MESSAGE = INBOUND_MESSAGE;
@@ -59,7 +64,9 @@ this.ContentSearch = {
receiveMessage: function (msg) {
let methodName = "on" + msg.data.type;
if (methodName in this) {
- this[methodName](msg, msg.data.data);
+ this._initService().then(() => {
+ this[methodName](msg, msg.data.data);
+ });
}
},
@@ -96,18 +103,20 @@ this.ContentSearch = {
},
observe: function (subj, topic, data) {
- switch (topic) {
- case "browser-search-engine-modified":
- if (data == "engine-current") {
- this._broadcast("CurrentEngine", this._currentEngineObj());
+ this._initService().then(() => {
+ switch (topic) {
+ case "browser-search-engine-modified":
+ if (data == "engine-current") {
+ this._broadcast("CurrentEngine", this._currentEngineObj());
+ }
+ else if (data != "engine-default") {
+ // engine-default is always sent with engine-current and isn't
+ // otherwise relevant to content searches.
+ this._broadcast("State", this._currentStateObj());
+ }
+ break;
}
- else if (data != "engine-default") {
- // engine-default is always sent with engine-current and isn't otherwise
- // relevant to content searches.
- this._broadcast("State", this._currentStateObj());
- }
- break;
- }
+ });
},
_reply: function (msg, type, data) {
@@ -146,4 +155,13 @@ this.ContentSearch = {
logo2xURI: Services.search.currentEngine.getIconURLBySize(130, 52),
};
},
+
+ _initService: function () {
+ if (!this._initServicePromise) {
+ let deferred = Promise.defer();
+ this._initServicePromise = deferred.promise;
+ Services.search.init(() => deferred.resolve());
+ }
+ return this._initServicePromise;
+ },
};
diff --git a/browser/themes/linux/devtools/computedview.css b/browser/themes/linux/devtools/computedview.css
deleted file mode 100644
index 31c4720d983a..000000000000
--- a/browser/themes/linux/devtools/computedview.css
+++ /dev/null
@@ -1,173 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* 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/. */
-
-* {
- box-sizing: border-box;
-}
-
-:root {
- height: 100%;
-}
-
-body {
- margin: 0;
- display : flex;
- flex-direction: column;
- height: 100%;
-}
-
-#propertyContainer {
- -moz-user-select: text;
- overflow: auto;
- min-height: 0;
- flex: 1;
-}
-
-.property-view-hidden,
-.property-content-hidden {
- display: none;
-}
-
-.property-view {
- clear: both;
- padding: 2px 0 2px 17px;
-}
-
-.property-view > * {
- display: inline-block;
- vertical-align: middle;
-}
-
-.property-name {
- /* -12px is so the expander triangle isn't pushed up above the property */
- width: calc(100% - 12px);
- overflow-x: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- outline: 0;
-}
-
-.property-value {
- width: 100%;
- overflow-x: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- background-image: url(arrow-e.png);
- background-repeat: no-repeat;
- background-size: 5px 8px;
- background-position: 2px center;
- padding-left: 10px;
- outline: 0;
-}
-
-.other-property-value {
- background-image: url(arrow-e.png);
- background-repeat: no-repeat;
- background-size: 5px 8px;
- background-position: left center;
- padding-left: 8px;
-}
-
-@media (min-width: 400px) {
- .property-name {
- width: 200px;
- }
- .property-value {
- /* -212px is accounting for the 200px property-name and the 12px triangle */
- width: calc(100% - 212px);
- }
-}
-
-.property-content {
- padding-left: 17px;
-}
-
-/* From skin */
-.expander {
- visibility: hidden;
- margin-left: -12px!important;
-}
-
-.expandable {
- visibility: visible;
-}
-
-.match {
- visibility: hidden;
-}
-
-.matchedselectors > p {
- clear: both;
- margin: 0 2px 0 0;
- padding: 2px;
- overflow-x: hidden;
- border-style: dotted;
- border-color: rgba(128,128,128,0.4);
- border-width: 1px 1px 0 1px;
-}
-
-.matchedselectors > p:last-of-type {
- border-bottom-width: 1px;
-}
-
-/* This rule is necessary because Templater.jsm breaks LTR TDs in RTL docs */
-.rule-text {
- direction: ltr;
-}
-
-.matched {
- text-decoration: line-through;
-}
-
-.parentmatch {
- opacity: 0.5;
-}
-
-#noResults {
- font-size: 110%;
- margin: 5px;
- text-align: center;
-}
-
-.onlyuserstyles {
- cursor: pointer;
-}
-
-.legendKey {
- margin: 0 5px;
-}
-
-.devtools-toolbar {
- width: 100%;
-}
-
-.link {
- padding: 0 3px;
- cursor: pointer;
- float: right;
-}
-
-.computedview-colorswatch {
- border-radius: 50%;
- width: 1em;
- height: 1em;
- vertical-align: text-top;
- -moz-margin-end: 5px;
- display: inline-block;
-}
-
-.computedview-colorswatch::before {
- content: '';
- background-color: #eee;
- background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
- linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
- background-size: 12px 12px;
- background-position: 0 0, 6px 6px;
- position: absolute;
- border-radius: 50%;
- width: 1em;
- height: 1em;
- z-index: -1;
-}
diff --git a/browser/themes/linux/downloads/indicator.css b/browser/themes/linux/downloads/indicator.css
index 6e2ef37bb9b3..388050f6b706 100644
--- a/browser/themes/linux/downloads/indicator.css
+++ b/browser/themes/linux/downloads/indicator.css
@@ -26,7 +26,7 @@
min-height: 18px;
}
-#downloads-button[cui-areatype="toolbar"] > #downloads-indicator-anchor > #downloads-indicator-icon:-moz-lwtheme-brighttext {
+toolbar[brighttext] #downloads-button[cui-areatype="toolbar"]:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon {
background: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"),
0, 198, 18, 180) center no-repeat;
}
@@ -49,8 +49,7 @@
background-size: 12px;
}
-#downloads-button:not([counter]) > #downloads-indicator-anchor >
-#downloads-button-progress-area > #downloads-indicator-counter:-moz-lwtheme-brighttext {
+toolbar[brighttext] #downloads-button:not([counter]):not([attention]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
}
@@ -115,7 +114,7 @@
text-align: center;
}
-#downloads-indicator-counter:-moz-lwtheme-brighttext {
+toolbar[brighttext] #downloads-indicator-counter {
color: white;
text-shadow: 0 0 1px rgba(0,0,0,.7),
0 1px 1.5px rgba(0,0,0,.5);
diff --git a/browser/themes/linux/jar.mn b/browser/themes/linux/jar.mn
index 127ca7b7bab7..b3bcdfaaf923 100644
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -237,8 +237,8 @@ browser.jar:
skin/classic/browser/devtools/magnifying-glass-light@2x.png (../shared/devtools/images/magnifying-glass-light@2x.png)
skin/classic/browser/devtools/itemToggle.png (../shared/devtools/images/itemToggle.png)
skin/classic/browser/devtools/itemToggle-light.png (../shared/devtools/images/itemToggle-light.png)
- skin/classic/browser/devtools/itemArrow-dark-rtl.png (../shared/devtools/images/itemArrow-dark-rtl.png)
- skin/classic/browser/devtools/itemArrow-dark-ltr.png (../shared/devtools/images/itemArrow-dark-ltr.png)
+ skin/classic/browser/devtools/itemArrow-dark-rtl.svg (../shared/devtools/images/itemArrow-dark-rtl.svg)
+ skin/classic/browser/devtools/itemArrow-dark-ltr.svg (../shared/devtools/images/itemArrow-dark-ltr.svg)
skin/classic/browser/devtools/itemArrow-rtl.svg (../shared/devtools/images/itemArrow-rtl.svg)
skin/classic/browser/devtools/itemArrow-ltr.svg (../shared/devtools/images/itemArrow-ltr.svg)
skin/classic/browser/devtools/background-noise-toolbar.png (../shared/devtools/images/background-noise-toolbar.png)
@@ -301,7 +301,7 @@ browser.jar:
skin/classic/browser/devtools/vview-open-inspector@2x.png (../shared/devtools/images/vview-open-inspector@2x.png)
skin/classic/browser/devtools/undock@2x.png (../shared/devtools/images/undock@2x.png)
skin/classic/browser/devtools/font-inspector.css (../shared/devtools/font-inspector.css)
- skin/classic/browser/devtools/computedview.css (devtools/computedview.css)
+ skin/classic/browser/devtools/computedview.css (../shared/devtools/computedview.css)
skin/classic/browser/devtools/arrow-e.png (../shared/devtools/images/arrow-e.png)
skin/classic/browser/devtools/responsiveui-rotate.png (../shared/devtools/responsiveui-rotate.png)
skin/classic/browser/devtools/responsiveui-touch.png (../shared/devtools/responsiveui-touch.png)
diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css
index 79fc9105dcf7..322d4d7cbfd0 100644
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -630,14 +630,6 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
%include ../shared/toolbarbuttons.inc.css
%include ../shared/menupanel.inc.css
- #home-button.bookmark-item {
- list-style-image: url("chrome://browser/skin/Toolbar.png");
- }
-
- #home-button.bookmark-item:not(@inAnyPanel@):-moz-lwtheme-brighttext {
- list-style-image: url(chrome://browser/skin/Toolbar-inverted.png);
- }
-
#back-button:hover:active:not([disabled="true"]) {
-moz-image-region: rect(18px, 36px, 36px, 18px);
}
@@ -801,14 +793,12 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
to avoid potentially breaking add-on toolbar buttons. */
:-moz-any(@primaryToolbarButtons@),
- #home-button.bookmark-item,
#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
list-style-image: url("chrome://browser/skin/Toolbar@2x.png");
}
- :-moz-any(@primaryToolbarButtons@):not(@inAnyPanel@):-moz-lwtheme-brighttext,
- #home-button.bookmark-item:not(@inAnyPanel@):-moz-lwtheme-brighttext,
- #bookmarks-menu-button:not(@inAnyPanel@):-moz-lwtheme-brighttext > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ toolbar[brighttext] :-moz-any(@primaryToolbarButtons@),
+ toolbar[brighttext] #bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
list-style-image: url("chrome://browser/skin/Toolbar-inverted@2x.png");
}
@@ -914,8 +904,8 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
-moz-image-region: rect(0, 36px, 36px, 0px);
}
- #sync-button[cui-areatype="toolbar"][status="active"]:-moz-lwtheme-brighttext,
- #sync-button[cui-areatype="toolbar"][status="active"]:-moz-lwtheme-brighttext:hover:active:not([disabled="true"]) {
+ toolbar[brighttext] #sync-button[cui-areatype="toolbar"][status="active"],
+ toolbar[brighttext] #sync-button[cui-areatype="toolbar"][status="active"]:hover:active:not([disabled="true"]) {
list-style-image: url("chrome://browser/skin/syncProgress-toolbar-inverted@2x.png");
}
diff --git a/browser/themes/osx/downloads/indicator.css b/browser/themes/osx/downloads/indicator.css
index d4960a7b1606..6ce0a1d3c5ee 100644
--- a/browser/themes/osx/downloads/indicator.css
+++ b/browser/themes/osx/downloads/indicator.css
@@ -29,11 +29,11 @@
0, 198, 18, 180) center no-repeat;
}
-#downloads-indicator-icon:-moz-lwtheme-brighttext {
+toolbar[brighttext] #downloads-indicator-icon {
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
}
-#downloads-button[attention] #downloads-indicator-icon {
+#downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
background-image: url("chrome://browser/skin/downloads/download-glow.png");
}
@@ -45,17 +45,17 @@
/* In the next few rules, we use :not([counter]) as a shortcut that is
equivalent to -moz-any([progress], [paused]). */
-#downloads-button:not([counter]) #downloads-indicator-counter {
+#downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
0, 198, 18, 180) center no-repeat;
background-size: 12px;
}
-#downloads-button:not([counter]) #downloads-indicator-counter:-moz-lwtheme-brighttext {
+toolbar[brighttext] #downloads-button:not([counter]):not([attention]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
}
-#downloads-button:not([counter])[attention] #downloads-indicator-counter {
+#downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
background-image: url("chrome://browser/skin/downloads/download-glow.png");
}
@@ -65,7 +65,7 @@
background-size: 18px;
}
- #downloads-indicator-icon:-moz-lwtheme-brighttext {
+ toolbar[brighttext] #downloads-indicator-icon {
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 0, 396, 36, 360);
}
@@ -73,7 +73,7 @@
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
}
- #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter:-moz-lwtheme-brighttext {
+ toolbar[brighttext] #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"),
0, 396, 36, 360);
}
diff --git a/browser/themes/osx/jar.mn b/browser/themes/osx/jar.mn
index 46cccd31c8fc..ae0273124732 100644
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -359,8 +359,8 @@ browser.jar:
skin/classic/browser/devtools/magnifying-glass-light@2x.png (../shared/devtools/images/magnifying-glass-light@2x.png)
skin/classic/browser/devtools/itemToggle.png (../shared/devtools/images/itemToggle.png)
skin/classic/browser/devtools/itemToggle-light.png (../shared/devtools/images/itemToggle-light.png)
- skin/classic/browser/devtools/itemArrow-dark-rtl.png (../shared/devtools/images/itemArrow-dark-rtl.png)
- skin/classic/browser/devtools/itemArrow-dark-ltr.png (../shared/devtools/images/itemArrow-dark-ltr.png)
+ skin/classic/browser/devtools/itemArrow-dark-rtl.svg (../shared/devtools/images/itemArrow-dark-rtl.svg)
+ skin/classic/browser/devtools/itemArrow-dark-ltr.svg (../shared/devtools/images/itemArrow-dark-ltr.svg)
skin/classic/browser/devtools/itemArrow-rtl.svg (../shared/devtools/images/itemArrow-rtl.svg)
skin/classic/browser/devtools/itemArrow-ltr.svg (../shared/devtools/images/itemArrow-ltr.svg)
skin/classic/browser/devtools/background-noise-toolbar.png (../shared/devtools/images/background-noise-toolbar.png)
@@ -423,7 +423,7 @@ browser.jar:
skin/classic/browser/devtools/vview-open-inspector@2x.png (../shared/devtools/images/vview-open-inspector@2x.png)
skin/classic/browser/devtools/undock@2x.png (../shared/devtools/images/undock@2x.png)
skin/classic/browser/devtools/font-inspector.css (../shared/devtools/font-inspector.css)
- skin/classic/browser/devtools/computedview.css (devtools/computedview.css)
+ skin/classic/browser/devtools/computedview.css (../shared/devtools/computedview.css)
skin/classic/browser/devtools/arrow-e.png (../shared/devtools/images/arrow-e.png)
skin/classic/browser/devtools/responsiveui-rotate.png (../shared/devtools/responsiveui-rotate.png)
skin/classic/browser/devtools/responsiveui-touch.png (../shared/devtools/responsiveui-touch.png)
diff --git a/browser/themes/shared/devtools/common.css b/browser/themes/shared/devtools/common.css
index dff74d91e7ad..4d345beed473 100644
--- a/browser/themes/shared/devtools/common.css
+++ b/browser/themes/shared/devtools/common.css
@@ -56,10 +56,8 @@
.devtools-autocomplete-popup {
-moz-appearance: none !important;
- border: 1px solid hsl(210,11%,10%);
box-shadow: 0 1px 0 hsla(209,29%,72%,.25) inset;
background-color: transparent;
- background-image: linear-gradient(to bottom, hsla(209,18%,18%,0.9), hsl(210,11%,16%));
border-radius: 3px;
overflow-x: hidden;
%if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_QT)
@@ -110,12 +108,24 @@
text-align: right;
}
-/* Rest of the light theme */
+/* Rest of the dark and light theme */
+
+.devtools-autocomplete-popup,
+.theme-dark .CodeMirror-hints,
+.theme-dark .CodeMirror-Tern-tooltip {
+ border: 1px solid hsl(210,11%,10%);
+ background-image: linear-gradient(to bottom, hsla(209,18%,18%,0.9), hsl(210,11%,16%));
+}
+
+.devtools-autocomplete-popup.light-theme,
+.light-theme .CodeMirror-hints,
+.light-theme .CodeMirror-Tern-tooltip {
+ border: 1px solid hsl(210,24%,90%);
+ background-image: linear-gradient(to bottom, hsla(209,18%,100%,0.9), hsl(210,24%,95%));
+}
.devtools-autocomplete-popup.light-theme {
- border: 1px solid hsl(210,24%,90%);
box-shadow: 0 1px 0 hsla(209,29%,90%,.25) inset;
- background-image: linear-gradient(to bottom, hsla(209,18%,100%,0.9), hsl(210,24%,95%));
}
.devtools-autocomplete-listbox.light-theme > richlistitem[selected],
diff --git a/browser/themes/osx/devtools/computedview.css b/browser/themes/shared/devtools/computedview.css
similarity index 97%
rename from browser/themes/osx/devtools/computedview.css
rename to browser/themes/shared/devtools/computedview.css
index 25c4bb45d40c..bcbdf3b6f438 100644
--- a/browser/themes/osx/devtools/computedview.css
+++ b/browser/themes/shared/devtools/computedview.css
@@ -3,22 +3,6 @@
* 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/. */
-/* Take away these two :visited rules to get a core dumper */
-/* See https://bugzilla.mozilla.org/show_bug.cgi?id=575675#c30 */
-.link,
-.link:visited {
- color: #0091ff;
-}
-.link,
-.helplink,
-.link:visited,
-.helplink:visited {
- text-decoration: none;
-}
-.link:hover {
- text-decoration: underline;
-}
-
/* From content */
* {
@@ -167,6 +151,25 @@ body {
float: right;
}
+/* Take away these two :visited rules to get a core dumper */
+/* See https://bugzilla.mozilla.org/show_bug.cgi?id=575675#c30 */
+
+.link,
+.link:visited {
+ color: #0091ff;
+}
+
+.link,
+.helplink,
+.link:visited,
+.helplink:visited {
+ text-decoration: none;
+}
+
+.link:hover {
+ text-decoration: underline;
+}
+
.computedview-colorswatch {
border-radius: 50%;
width: 1em;
@@ -174,6 +177,7 @@ body {
vertical-align: text-top;
-moz-margin-end: 5px;
display: inline-block;
+ position: relative;
}
.computedview-colorswatch::before {
@@ -185,7 +189,9 @@ body {
background-position: 0 0, 6px 6px;
position: absolute;
border-radius: 50%;
- width: 1em;
- height: 1em;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
z-index: -1;
}
diff --git a/browser/themes/shared/devtools/dark-theme.css b/browser/themes/shared/devtools/dark-theme.css
index ec7d2ec49870..368b551e2e93 100644
--- a/browser/themes/shared/devtools/dark-theme.css
+++ b/browser/themes/shared/devtools/dark-theme.css
@@ -25,7 +25,8 @@
background-color: rgba(0,0,0,0.5);
}
-.theme-selected {
+.theme-selected,
+.CodeMirror-hint-active {
background-color: #1d4f73;
color: #f5f7fa; /* Light foreground text */
}
@@ -45,7 +46,8 @@
* failures in debug builds.
*/
.theme-link:visited,
-.cm-s-mozilla .cm-link:visited { /* blue */
+.cm-s-mozilla .cm-link:visited,
+.CodeMirror-Tern-type { /* blue */
color: #3689b2;
}
@@ -59,6 +61,10 @@
color: #5c6773;
}
+.CodeMirror-Tern-completion-unknown:before {
+ background-color: #5c6773;
+}
+
.theme-gutter {
background-color: #0f171f;
color: #667380;
@@ -77,6 +83,10 @@
color: #5c9966;
}
+.CodeMirror-Tern-completion-number:before {
+ background-color: #5c9966;
+}
+
.theme-fg-color2,
.cm-s-mozilla .cm-attribute,
.cm-s-mozilla .cm-variable,
@@ -87,6 +97,10 @@
color: #3689b2;
}
+.CodeMirror-Tern-completion-object:before {
+ background-color: #3689b2;
+}
+
.theme-fg-color3,
.cm-s-mozilla .cm-builtin,
.cm-s-mozilla .cm-tag,
@@ -95,6 +109,10 @@
color: #a673bf;
}
+.CodeMirror-Tern-completion-array:before {
+ background-color: #a673bf;
+}
+
.theme-fg-color4 { /* purple/violet */
color: #6270b2;
}
@@ -108,10 +126,16 @@
.theme-fg-color6,
.cm-s-mozilla .cm-string,
.cm-s-mozilla .cm-string-2,
-.variable-or-property .token-string { /* Orange */
+.variable-or-property .token-string,
+.CodeMirror-Tern-farg { /* Orange */
color: #b26b47;
}
+.CodeMirror-Tern-completion-string:before,
+.CodeMirror-Tern-completion-fn:before {
+ background-color: #b26b47;
+}
+
.theme-fg-color7,
.cm-s-mozilla .cm-atom,
.cm-s-mozilla .cm-quote,
@@ -122,6 +146,10 @@
color: #bf5656;
}
+.CodeMirror-Tern-completion-bool:before {
+ background-color: #bf5656;
+}
+
.variable-or-property .token-domnode {
font-weight: bold;
}
@@ -334,4 +362,16 @@ div.CodeMirror span.eval-text {
color: rgba(184, 200, 217, 1);
}
+.CodeMirror-Tern-fname {
+ color: #f7f7f7;
+}
+
+.CodeMirror-hints,
+.CodeMirror-Tern-tooltip {
+ box-shadow: 0 0 4px rgba(255, 255, 255, .3);
+ background-color: #0f171f;
+ color: #8fa1b2;
+}
+
+
%include toolbars.inc.css
diff --git a/browser/themes/shared/devtools/images/itemArrow-dark-ltr.png b/browser/themes/shared/devtools/images/itemArrow-dark-ltr.png
deleted file mode 100644
index 375dd86f606e..000000000000
Binary files a/browser/themes/shared/devtools/images/itemArrow-dark-ltr.png and /dev/null differ
diff --git a/browser/themes/shared/devtools/images/itemArrow-dark-ltr.svg b/browser/themes/shared/devtools/images/itemArrow-dark-ltr.svg
new file mode 100644
index 000000000000..7bd7028ace92
--- /dev/null
+++ b/browser/themes/shared/devtools/images/itemArrow-dark-ltr.svg
@@ -0,0 +1,4 @@
+
diff --git a/browser/themes/shared/devtools/images/itemArrow-dark-rtl.png b/browser/themes/shared/devtools/images/itemArrow-dark-rtl.png
deleted file mode 100644
index 5a639fd0d470..000000000000
Binary files a/browser/themes/shared/devtools/images/itemArrow-dark-rtl.png and /dev/null differ
diff --git a/browser/themes/shared/devtools/images/itemArrow-dark-rtl.svg b/browser/themes/shared/devtools/images/itemArrow-dark-rtl.svg
new file mode 100644
index 000000000000..9315d9e56abe
--- /dev/null
+++ b/browser/themes/shared/devtools/images/itemArrow-dark-rtl.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/browser/themes/shared/devtools/light-theme.css b/browser/themes/shared/devtools/light-theme.css
index 68ecfacfbd82..b58ff015a483 100644
--- a/browser/themes/shared/devtools/light-theme.css
+++ b/browser/themes/shared/devtools/light-theme.css
@@ -25,7 +25,8 @@
background: #EFEFEF;
}
-.theme-selected {
+.theme-selected,
+.CodeMirror-hint-active {
background-color: #4c9ed9;
color: #f5f7fa; /* Light foreground text */
}
@@ -36,7 +37,8 @@
}
.theme-link,
-.cm-s-mozilla .cm-link { /* blue */
+.cm-s-mozilla .cm-link,
+.CodeMirror-Tern-type { /* blue */
color: hsl(208,56%,40%);
}
@@ -58,6 +60,10 @@
color: hsl(90,2%,46%);
}
+.CodeMirror-Tern-completion-unknown:before {
+ background-color: hsl(90,2%,46%);
+}
+
.theme-gutter {
background-color: hsl(0,0%,90%);
color: #667380;
@@ -76,6 +82,10 @@
color: hsl(72,100%,27%);
}
+.CodeMirror-Tern-completion-number:before {
+ background-color: hsl(72,100%,27%);
+}
+
.theme-fg-color2,
.cm-s-mozilla .cm-attribute,
.cm-s-mozilla .cm-builtin,
@@ -86,12 +96,20 @@
color: hsl(208,56%,40%);
}
+.CodeMirror-Tern-completion-object:before {
+ background-color: hsl(208,56%,40%);
+}
+
.theme-fg-color3,
.cm-s-mozilla .cm-variable,
.cm-s-mozilla .cm-tag,
.cm-s-mozilla .cm-header,
.variables-view-property > .title > .name { /* dark blue */
- color: hsl(208,81%,21%)
+ color: hsl(208,81%,21%);
+}
+
+.CodeMirror-Tern-completion-array:before { /* dark blue */
+ background-color: hsl(208,81%,21%);
}
.theme-fg-color4 { /* Orange */
@@ -107,10 +125,16 @@
.theme-fg-color6,
.cm-s-mozilla .cm-string,
.cm-s-mozilla .cm-string-2,
-.variable-or-property .token-string { /* Orange */
+.variable-or-property .token-string,
+.CodeMirror-Tern-farg { /* Orange */
color: hsl(24,85%,39%);
}
+.CodeMirror-Tern-completion-string:before,
+.CodeMirror-Tern-completion-fn:before {
+ background-color: hsl(24,85%,39%);
+}
+
.theme-fg-color7,
.cm-s-mozilla .cm-atom,
.cm-s-mozilla .cm-quote,
@@ -121,6 +145,10 @@
color: #bf5656;
}
+.CodeMirror-Tern-completion-bool:before {
+ background-color: #bf5656;
+}
+
.variable-or-property .token-domnode {
font-weight: bold;
}
@@ -337,4 +365,11 @@ div.CodeMirror span.eval-text {
border-color: #aaa; /* Needed for responsive container at low width. */
}
+.CodeMirror-hints,
+.CodeMirror-Tern-tooltip {
+ box-shadow: 0 0 4px rgba(128, 128, 128, .5);
+ background-color: #f7f7f7;
+}
+
+
%include toolbars.inc.css
diff --git a/browser/themes/shared/devtools/ruleview.css b/browser/themes/shared/devtools/ruleview.css
index b4821a50a0c3..c43fc377abe5 100644
--- a/browser/themes/shared/devtools/ruleview.css
+++ b/browser/themes/shared/devtools/ruleview.css
@@ -119,6 +119,7 @@
vertical-align: text-top;
-moz-margin-end: 5px;
display: inline-block;
+ position: relative;
}
.ruleview-colorswatch::before {
@@ -130,8 +131,10 @@
background-position: 0 0, 6px 6px;
position: absolute;
border-radius: 50%;
- width: 1em;
- height: 1em;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
z-index: -1;
}
diff --git a/browser/themes/shared/devtools/splitview.css b/browser/themes/shared/devtools/splitview.css
index 07f75eb9bd5d..44767e82b59c 100644
--- a/browser/themes/shared/devtools/splitview.css
+++ b/browser/themes/shared/devtools/splitview.css
@@ -90,13 +90,13 @@
}
.theme-dark .splitview-nav > li.splitview-active {
- background-image: url(itemArrow-dark-ltr.png),
+ background-image: url(itemArrow-dark-ltr.svg),
linear-gradient(@smw_marginDark@, @smw_marginDark@),
linear-gradient(#1d4f73, #1d4f73);
}
.theme-dark .splitview-nav > li.splitview-active:-moz-locale-dir(rtl) {
- background-image: url(itemArrow-dark-rtl.png),
+ background-image: url(itemArrow-dark-rtl.svg),
linear-gradient(@smw_marginDark@, @smw_marginDark@),
linear-gradient(#1d4f73, #1d4f73);
}
diff --git a/browser/themes/shared/devtools/widgets.inc.css b/browser/themes/shared/devtools/widgets.inc.css
index 01d3e3b7e867..df1cb9117933 100644
--- a/browser/themes/shared/devtools/widgets.inc.css
+++ b/browser/themes/shared/devtools/widgets.inc.css
@@ -478,11 +478,11 @@
}
.theme-dark .side-menu-widget-item.selected > .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
- background-image: url(itemArrow-dark-ltr.png), linear-gradient(to right, @smw_marginDark@, @smw_marginDark@);
+ background-image: url(itemArrow-dark-ltr.svg), linear-gradient(to right, @smw_marginDark@, @smw_marginDark@);
}
.theme-dark .side-menu-widget-item.selected > .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
- background-image: url(itemArrow-dark-rtl.png), linear-gradient(to right, @smw_marginDark@, @smw_marginDark@);
+ background-image: url(itemArrow-dark-rtl.svg), linear-gradient(to right, @smw_marginDark@, @smw_marginDark@);
}
.theme-light .side-menu-widget-item.selected > .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
diff --git a/browser/themes/shared/toolbarbuttons.inc.css b/browser/themes/shared/toolbarbuttons.inc.css
index 89a7645145d6..bba263359fc8 100644
--- a/browser/themes/shared/toolbarbuttons.inc.css
+++ b/browser/themes/shared/toolbarbuttons.inc.css
@@ -5,8 +5,8 @@
list-style-image: url("chrome://browser/skin/Toolbar.png");
}
-:-moz-any(@primaryToolbarButtons@):not(@inAnyPanel@):-moz-lwtheme-brighttext,
-#bookmarks-menu-button:not(@inAnyPanel@):-moz-lwtheme-brighttext > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+toolbar[brighttext] :-moz-any(@primaryToolbarButtons@),
+toolbar[brighttext] #bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
list-style-image: url(chrome://browser/skin/Toolbar-inverted.png);
}
@@ -64,8 +64,8 @@
-moz-image-region: rect(0, 18px, 18px, 0px);
}
-#sync-button[cui-areatype="toolbar"][status="active"]:-moz-lwtheme-brighttext,
-#sync-button[cui-areatype="toolbar"][status="active"]:-moz-lwtheme-brighttext:hover:active:not([disabled="true"]) {
+toolbar[brighttext] #sync-button[status="active"],
+toolbar[brighttext] #sync-button[status="active"]:hover:active:not([disabled="true"]) {
list-style-image: url("chrome://browser/skin/syncProgress-toolbar-inverted.png");
}
diff --git a/browser/themes/windows/browser-aero.css b/browser/themes/windows/browser-aero.css
index b7ee8c9b5247..2f7cc3dde9d4 100644
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -165,27 +165,6 @@
color: white;
}
- #main-window[darkwindowframe="true"] :-moz-any(#toolbar-menubar, #TabsToolbar) :-moz-any(@primaryToolbarButtons@):not(:-moz-lwtheme):not(:-moz-window-inactive),
- #main-window[darkwindowframe="true"] :-moz-any(#toolbar-menubar, #TabsToolbar) #bookmarks-menu-button:not(:-moz-lwtheme) > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:not(:-moz-window-inactive) {
- list-style-image: url(chrome://browser/skin/Toolbar-inverted.png);
- }
-
- #main-window[darkwindowframe="true"] .tabs-newtab-button:not(:-moz-lwtheme):not(:-moz-window-inactive),
- #main-window[darkwindowframe="true"] :-moz-any(#toolbar-menubar, #TabsToolbar) > #new-tab-button:not(:-moz-lwtheme):not(:-moz-window-inactive),
- #main-window[darkwindowframe="true"] :-moz-any(#toolbar-menubar, #TabsToolbar) > toolbarpaletteitem > #new-tab-button:not(:-moz-lwtheme):not(:-moz-window-inactive) {
- list-style-image: url(chrome://browser/skin/tabbrowser/newtab-inverted.png);
- }
-
- #main-window[darkwindowframe="true"] .tab-close-button:not(:-moz-any(:hover, [selected="true"], :-moz-lwtheme, :-moz-window-inactive)) {
- -moz-image-region: rect(0, 64px, 16px, 48px);
- }
-
- #main-window[darkwindowframe="true"] :-moz-any(#toolbar-menubar, #TabsToolbar) > #sync-button[status="active"]:not(:-moz-lwtheme),
- #main-window[darkwindowframe="true"] :-moz-any(#toolbar-menubar, #TabsToolbar) > toolbarpaletteitem > #sync-button[status="active"]:not(:-moz-lwtheme) {
- list-style-image: url("chrome://browser/skin/syncProgress-toolbar-inverted.png");
- }
-
-
#toolbar-menubar:not(:-moz-lwtheme) {
text-shadow: 0 0 .5em white, 0 0 .5em white, 0 1px 0 rgba(255,255,255,.4);
}
@@ -283,21 +262,6 @@
background-color: #556;
}
- /* Use inverted icons for non-fogged glassed toolbars */
- #toolbar-menubar > toolbarpaletteitem > #bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:not(:-moz-lwtheme),
- #toolbar-menubar > #bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:not(:-moz-lwtheme),
- #toolbar-menubar > toolbarpaletteitem > toolbaritem > :-moz-any(@nestedButtons@):not(:-moz-lwtheme),
- #toolbar-menubar > toolbaritem > :-moz-any(@nestedButtons@):not(:-moz-lwtheme),
- #toolbar-menubar > toolbarpaletteitem > :-moz-any(@primaryToolbarButtons@):not(:-moz-lwtheme),
- #toolbar-menubar > :-moz-any(@primaryToolbarButtons@):not(:-moz-lwtheme) {
- list-style-image: url("chrome://browser/skin/Toolbar-inverted.png");
- }
-
- #toolbar-menubar > #sync-button[status="active"]:not(:-moz-lwtheme),
- #toolbar-menubar > toolbarpaletteitem > #sync-button[status="active"]:not(:-moz-lwtheme) {
- list-style-image: url("chrome://browser/skin/syncProgress-toolbar-inverted.png");
- }
-
/* Glass Fog */
#TabsToolbar:not(:-moz-lwtheme) {
diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css
index cc96a9f6668a..c94678e6c8b5 100644
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -39,8 +39,8 @@
margin-top: 3px;
}
-#main-window[tabsintitlebar][sizemode="normal"][chromehidden~="menubar"] #toolbar-menubar ~ #TabsToolbar,
-#main-window[tabsintitlebar][sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] ~ #TabsToolbar {
+#main-window[tabsintitlebar][sizemode="normal"]:not([inFullscreen])[chromehidden~="menubar"] #toolbar-menubar ~ #TabsToolbar,
+#main-window[tabsintitlebar][sizemode="normal"]:not([inFullscreen]) #toolbar-menubar[autohide="true"][inactive] ~ #TabsToolbar {
margin-top: 15px;
}
@@ -198,10 +198,6 @@
border-bottom: 1px solid transparent;
}
- #main-window[tabsintitlebar]:not([inFullscreen]) .tab-close-button:not(:-moz-any(:hover,:-moz-lwtheme,[selected="true"])) {
- -moz-image-region: rect(0, 64px, 16px, 48px);
- }
-
#main-window[tabsintitlebar][sizemode="normal"] #titlebar-content:-moz-lwtheme {
/* Render a window top border: */
background-image: linear-gradient(to bottom,
@@ -575,8 +571,8 @@ menuitem.bookmark-item {
list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow.png");
}
-toolbar .toolbarbutton-1 > .toolbarbutton-menu-dropmarker:-moz-lwtheme-brighttext,
-toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker:-moz-lwtheme-brighttext {
+toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menu-dropmarker,
+toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow-inverted.png");
}
@@ -1000,35 +996,6 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
list-style-image: url("chrome://browser/skin/menu-forward.png") !important;
}
-#home-button.bookmark-item {
- list-style-image: url("chrome://browser/skin/Toolbar.png");
-}
-
-%ifndef WINDOWS_AERO
-@media (-moz-windows-theme: luna-silver) {
- #home-button.bookmark-item {
- list-style-image: url("chrome://browser/skin/Toolbar-lunaSilver.png");
- }
-}
-%endif
-
-#main-window[tabsintitlebar]:not([inFullscreen]) :-moz-any(#TabsToolbar, #toolbar-menubar) > #bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:-moz-system-metric(windows-classic):not(:-moz-lwtheme),
-#main-window[tabsintitlebar]:not([inFullscreen]) :-moz-any(#TabsToolbar, #toolbar-menubar) > toolbarpaletteitem > #bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:-moz-system-metric(windows-classic):not(:-moz-lwtheme),
-#main-window[tabsintitlebar]:not([inFullscreen]) :-moz-any(#TabsToolbar, #toolbar-menubar) > toolbarpaletteitem > toolbaritem > :-moz-any(@nestedButtons@):-moz-system-metric(windows-classic):not(:-moz-lwtheme),
-#main-window[tabsintitlebar]:not([inFullscreen]) :-moz-any(#TabsToolbar, #toolbar-menubar) > toolbaritem > :-moz-any(@nestedButtons@):-moz-system-metric(windows-classic):not(:-moz-lwtheme),
-#main-window[tabsintitlebar]:not([inFullscreen]) :-moz-any(#TabsToolbar, #toolbar-menubar) > toolbarpaletteitem > :-moz-any(@primaryToolbarButtons@):-moz-system-metric(windows-classic):not(:-moz-lwtheme),
-#main-window[tabsintitlebar]:not([inFullscreen]) :-moz-any(#TabsToolbar, #toolbar-menubar) > :-moz-any(@primaryToolbarButtons@):-moz-system-metric(windows-classic):not(:-moz-lwtheme),
-#home-button.bookmark-item:-moz-lwtheme-brighttext {
- position: relative;
- z-index: 1;
- list-style-image: url("chrome://browser/skin/Toolbar-inverted.png");
-}
-
-#main-window[tabsintitlebar]:not([inFullscreen]) :-moz-any(#TabsToolbar, #toolbar-menubar) > #sync-button[status="active"]:-moz-system-metric(windows-classic):not(:-moz-lwtheme),
-#main-window[tabsintitlebar]:not([inFullscreen]) :-moz-any(#TabsToolbar, #toolbar-menubar) > toolbarpaletteitem > #sync-button[status="active"]:-moz-system-metric(windows-classic):not(:-moz-lwtheme) {
- list-style-image: url("chrome://browser/skin/syncProgress-toolbar-inverted.png");
-}
-
/* tabview button & menu item */
#menu_tabview {
@@ -1836,25 +1803,6 @@ toolbarbutton[type="socialmark"] > .toolbarbutton-icon {
%ifndef WINDOWS_AERO
/* Use lighter colors of buttons and text in the titlebar on luna-blue */
@media (-moz-windows-theme: luna-blue) {
- #main-window[tabsintitlebar]:not([inFullscreen]) .tabbrowser-arrowscrollbox > .scrollbutton-up,
- #main-window[tabsintitlebar]:not([inFullscreen]) .tabbrowser-arrowscrollbox > .scrollbutton-down {
- list-style-image: url(chrome://browser/skin/tabbrowser/tab-arrow-left-inverted.png);
- }
-
- #main-window[tabsintitlebar]:not([inFullscreen]) .tabs-newtab-button,
- #main-window[tabsintitlebar]:not([inFullscreen]) #TabsToolbar > #new-tab-button,
- #main-window[tabsintitlebar]:not([inFullscreen]) #TabsToolbar > toolbarpaletteitem > #new-tab-button {
- list-style-image: url(chrome://browser/skin/tabbrowser/newtab-inverted.png);
- }
-
- #main-window[tabsintitlebar]:not([inFullscreen]) #alltabs-button {
- list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow-inverted.png");
- }
-
- #main-window[tabsintitlebar]:not([inFullscreen]) .tab-close-button:not(:-moz-any(:hover,:-moz-lwtheme,[selected="true"])) {
- -moz-image-region: rect(0, 64px, 16px, 48px);
- }
-
#tabbrowser-tabs[movingtab] > .tabbrowser-tab[beforeselected]:not([last-visible-tab])::after,
.tabbrowser-tab:not([selected]):not([afterselected-visible]):not([afterhovered]):not([first-visible-tab]):not(:hover)::before,
#tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([selected]):not([beforehovered]):not(:hover)::after {
@@ -1863,7 +1811,7 @@ toolbarbutton[type="socialmark"] > .toolbarbutton-icon {
}
%endif
-.tab-close-button:not(:hover):not([selected="true"]):-moz-lwtheme-brighttext {
+#TabsToolbar[brighttext] .tab-close-button:not(:hover):not([selected="true"]) {
-moz-image-region: rect(0, 64px, 16px, 48px) !important;
}
@@ -1906,10 +1854,8 @@ toolbarbutton[type="socialmark"] > .toolbarbutton-icon {
background-origin: border-box;
}
-#main-window[tabsintitlebar]:not([inFullscreen]) .tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-system-metric(windows-classic):not(:-moz-lwtheme),
-#main-window[tabsintitlebar]:not([inFullscreen]) .tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-system-metric(windows-classic):not(:-moz-lwtheme),
-.tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-lwtheme-brighttext,
-.tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-lwtheme-brighttext {
+#TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .scrollbutton-up,
+#TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .scrollbutton-down {
list-style-image: url(chrome://browser/skin/tabbrowser/tab-arrow-left-inverted.png);
}
@@ -1951,12 +1897,9 @@ toolbarbutton[type="socialmark"] > .toolbarbutton-icon {
-moz-image-region: auto;
}
-#main-window[tabsintitlebar]:not([inFullscreen]) .tabs-newtab-button:-moz-system-metric(windows-classic):not(:-moz-lwtheme),
-#main-window[tabsintitlebar]:not([inFullscreen]) #TabsToolbar > #new-tab-button:-moz-system-metric(windows-classic):not(:-moz-lwtheme),
-#main-window[tabsintitlebar]:not([inFullscreen]) #TabsToolbar > toolbarpaletteitem > #new-tab-button:-moz-system-metric(windows-classic):not(:-moz-lwtheme),
-.tabs-newtab-button:-moz-lwtheme-brighttext,
-#TabsToolbar > #new-tab-button:-moz-lwtheme-brighttext,
-#TabsToolbar > toolbarpaletteitem > #new-tab-button:-moz-lwtheme-brighttext {
+#TabsToolbar[brighttext] .tabs-newtab-button,
+#TabsToolbar[brighttext] > #new-tab-button,
+#TabsToolbar[brighttext] > toolbarpaletteitem > #new-tab-button {
list-style-image: url(chrome://browser/skin/tabbrowser/newtab-inverted.png);
}
@@ -1968,8 +1911,8 @@ toolbarbutton[type="socialmark"] > .toolbarbutton-icon {
list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow.png");
}
-#main-window[tabsintitlebar]:not([inFullscreen]) #alltabs-button:-moz-system-metric(windows-classic):not(:-moz-lwtheme),
-#alltabs-button:-moz-lwtheme-brighttext {
+#TabsToolbar[brighttext] > #alltabs-button,
+#TabsToolbar[brighttext] > toolbarpaletteitem > #alltabs-button {
list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow-inverted.png");
}
@@ -2620,7 +2563,7 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
list-style-image: url(chrome://browser/skin/Metro_Glyph.png);
}
-#switch-to-metro-button[cui-areatype="toolbar"]:-moz-lwtheme-brighttext {
+toolbar[brighttext] #switch-to-metro-button[cui-areatype="toolbar"] {
list-style-image: url(chrome://browser/skin/Metro_Glyph-inverted.png);
}
diff --git a/browser/themes/windows/devtools/computedview.css b/browser/themes/windows/devtools/computedview.css
deleted file mode 100644
index 25c4bb45d40c..000000000000
--- a/browser/themes/windows/devtools/computedview.css
+++ /dev/null
@@ -1,191 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* 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/. */
-
-/* Take away these two :visited rules to get a core dumper */
-/* See https://bugzilla.mozilla.org/show_bug.cgi?id=575675#c30 */
-.link,
-.link:visited {
- color: #0091ff;
-}
-.link,
-.helplink,
-.link:visited,
-.helplink:visited {
- text-decoration: none;
-}
-.link:hover {
- text-decoration: underline;
-}
-
-/* From content */
-
-* {
- box-sizing: border-box;
-}
-
-:root {
- height: 100%;
-}
-
-body {
- margin: 0;
- display : flex;
- flex-direction: column;
- height: 100%;
-}
-
-#propertyContainer {
- -moz-user-select: text;
- overflow: auto;
- min-height: 0;
- flex: 1;
-}
-
-.property-view-hidden,
-.property-content-hidden {
- display: none;
-}
-
-.property-view {
- clear: both;
- padding: 2px 0 2px 17px;
-}
-
-.property-view > * {
- display: inline-block;
- vertical-align: middle;
-}
-
-.property-name {
- /* -12px is so the expander triangle isn't pushed up above the property */
- width: calc(100% - 12px);
- overflow-x: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- outline: 0;
-}
-
-.property-value {
- width: 100%;
- overflow-x: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- background-image: url(arrow-e.png);
- background-repeat: no-repeat;
- background-size: 5px 8px;
- background-position: 2px center;
- padding-left: 10px;
- outline: 0;
-}
-
-.other-property-value {
- background-image: url(arrow-e.png);
- background-repeat: no-repeat;
- background-size: 5px 8px;
- background-position: left center;
- padding-left: 8px;
-}
-
-@media (min-width: 400px) {
- .property-name {
- width: 200px;
- }
- .property-value {
- /* -212px is accounting for the 200px property-name and the 12px triangle */
- width: calc(100% - 212px);
- }
-}
-
-.property-content {
- padding-left: 17px;
-}
-
-/* From skin */
-.expander {
- visibility: hidden;
- margin-left: -12px!important;
-}
-
-.expandable {
- visibility: visible;
-}
-
-.match {
- visibility: hidden;
-}
-
-.matchedselectors > p {
- clear: both;
- margin: 0 2px 0 0;
- padding: 2px;
- overflow-x: hidden;
- border-style: dotted;
- border-color: rgba(128,128,128,0.4);
- border-width: 1px 1px 0 1px;
-}
-
-.matchedselectors > p:last-of-type {
- border-bottom-width: 1px;
-}
-
-/* This rule is necessary because Templater.jsm breaks LTR TDs in RTL docs */
-.rule-text {
- direction: ltr;
-}
-
-.matched {
- text-decoration: line-through;
-}
-
-.parentmatch {
- opacity: 0.5;
-}
-
-#noResults {
- font-size: 110%;
- margin: 5px;
- text-align: center;
-}
-
-.onlyuserstyles {
- cursor: pointer;
-}
-
-.legendKey {
- margin: 0 5px;
-}
-
-.devtools-toolbar {
- width: 100%;
-}
-
-.link {
- padding: 0 3px;
- cursor: pointer;
- float: right;
-}
-
-.computedview-colorswatch {
- border-radius: 50%;
- width: 1em;
- height: 1em;
- vertical-align: text-top;
- -moz-margin-end: 5px;
- display: inline-block;
-}
-
-.computedview-colorswatch::before {
- content: '';
- background-color: #eee;
- background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
- linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
- background-size: 12px 12px;
- background-position: 0 0, 6px 6px;
- position: absolute;
- border-radius: 50%;
- width: 1em;
- height: 1em;
- z-index: -1;
-}
diff --git a/browser/themes/windows/downloads/indicator-aero.css b/browser/themes/windows/downloads/indicator-aero.css
index 1f1b354e77c0..27cf815a4a32 100644
--- a/browser/themes/windows/downloads/indicator-aero.css
+++ b/browser/themes/windows/downloads/indicator-aero.css
@@ -2,28 +2,7 @@
* 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/. */
-@media (-moz-windows-glass) {
- /* The following rules are for the downloads indicator when in its normal,
- non-downloading, non-paused state (ie, it's just showing the downloads
- button icon). */
- #toolbar-menubar #downloads-button:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon:not(:-moz-lwtheme),
-
- /* The following rules are for the downloads indicator when in its paused
- or undetermined progress state. We use :not([counter]) as a shortcut for
- :-moz-any([progress], [paused]). */
-
- /* This is the case where the downloads indicator has been moved next to the menubar. */
- #toolbar-menubar #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
- background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
- }
-
- #toolbar-menubar #downloads-indicator-counter:not(:-moz-lwtheme) {
- color: white;
- text-shadow: 0 0 1px rgba(0,0,0,.7),
- 0 1px 1.5px rgba(0,0,0,.5);
- }
-}
-
#downloads-indicator-counter {
+ /* Bug 812345 added this... */
margin-bottom: -1px;
}
diff --git a/browser/themes/windows/downloads/indicator.css b/browser/themes/windows/downloads/indicator.css
index 813f28380b08..783a7e78e762 100644
--- a/browser/themes/windows/downloads/indicator.css
+++ b/browser/themes/windows/downloads/indicator.css
@@ -26,7 +26,7 @@
min-height: 18px;
}
-#downloads-indicator-icon:-moz-lwtheme-brighttext {
+toolbar[brighttext] #downloads-button:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon {
background: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"),
0, 198, 18, 180) center no-repeat;
}
@@ -64,8 +64,7 @@
background-size: 12px;
}
-#downloads-button:not([counter]) > #downloads-indicator-anchor >
-#downloads-button-progress-area > #downloads-indicator-counter:-moz-lwtheme-brighttext {
+toolbar[brighttext] #downloads-button:not([counter]):not([attention]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
}
@@ -130,7 +129,7 @@
text-align: center;
}
-#downloads-indicator-counter:-moz-lwtheme-brighttext {
+toolbar[brighttext] #downloads-indicator-counter {
color: white;
text-shadow: 0 0 1px rgba(0,0,0,.7),
0 1px 1.5px rgba(0,0,0,.5);
diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn
index 2771b115ec7a..dd39c24fea87 100644
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -275,8 +275,8 @@ browser.jar:
skin/classic/browser/devtools/magnifying-glass-light@2x.png (../shared/devtools/images/magnifying-glass-light@2x.png)
skin/classic/browser/devtools/itemToggle.png (../shared/devtools/images/itemToggle.png)
skin/classic/browser/devtools/itemToggle-light.png (../shared/devtools/images/itemToggle-light.png)
- skin/classic/browser/devtools/itemArrow-dark-rtl.png (../shared/devtools/images/itemArrow-dark-rtl.png)
- skin/classic/browser/devtools/itemArrow-dark-ltr.png (../shared/devtools/images/itemArrow-dark-ltr.png)
+ skin/classic/browser/devtools/itemArrow-dark-rtl.svg (../shared/devtools/images/itemArrow-dark-rtl.svg)
+ skin/classic/browser/devtools/itemArrow-dark-ltr.svg (../shared/devtools/images/itemArrow-dark-ltr.svg)
skin/classic/browser/devtools/itemArrow-rtl.svg (../shared/devtools/images/itemArrow-rtl.svg)
skin/classic/browser/devtools/itemArrow-ltr.svg (../shared/devtools/images/itemArrow-ltr.svg)
skin/classic/browser/devtools/background-noise-toolbar.png (../shared/devtools/images/background-noise-toolbar.png)
@@ -338,7 +338,7 @@ browser.jar:
skin/classic/browser/devtools/vview-open-inspector@2x.png (../shared/devtools/images/vview-open-inspector@2x.png)
skin/classic/browser/devtools/undock@2x.png (../shared/devtools/images/undock@2x.png)
skin/classic/browser/devtools/font-inspector.css (../shared/devtools/font-inspector.css)
- skin/classic/browser/devtools/computedview.css (devtools/computedview.css)
+ skin/classic/browser/devtools/computedview.css (../shared/devtools/computedview.css)
skin/classic/browser/devtools/arrow-e.png (../shared/devtools/images/arrow-e.png)
skin/classic/browser/devtools/responsiveui-rotate.png (../shared/devtools/responsiveui-rotate.png)
skin/classic/browser/devtools/responsiveui-touch.png (../shared/devtools/responsiveui-touch.png)
@@ -661,8 +661,8 @@ browser.jar:
skin/classic/aero/browser/devtools/magnifying-glass-light@2x.png (../shared/devtools/images/magnifying-glass-light@2x.png)
skin/classic/aero/browser/devtools/itemToggle.png (../shared/devtools/images/itemToggle.png)
skin/classic/aero/browser/devtools/itemToggle-light.png (../shared/devtools/images/itemToggle-light.png)
- skin/classic/aero/browser/devtools/itemArrow-dark-rtl.png (../shared/devtools/images/itemArrow-dark-rtl.png)
- skin/classic/aero/browser/devtools/itemArrow-dark-ltr.png (../shared/devtools/images/itemArrow-dark-ltr.png)
+ skin/classic/aero/browser/devtools/itemArrow-dark-rtl.svg (../shared/devtools/images/itemArrow-dark-rtl.svg)
+ skin/classic/aero/browser/devtools/itemArrow-dark-ltr.svg (../shared/devtools/images/itemArrow-dark-ltr.svg)
skin/classic/aero/browser/devtools/itemArrow-rtl.svg (../shared/devtools/images/itemArrow-rtl.svg)
skin/classic/aero/browser/devtools/itemArrow-ltr.svg (../shared/devtools/images/itemArrow-ltr.svg)
skin/classic/aero/browser/devtools/background-noise-toolbar.png (../shared/devtools/images/background-noise-toolbar.png)
@@ -724,7 +724,7 @@ browser.jar:
skin/classic/aero/browser/devtools/vview-open-inspector@2x.png (../shared/devtools/images/vview-open-inspector@2x.png)
skin/classic/aero/browser/devtools/undock@2x.png (../shared/devtools/images/undock@2x.png)
skin/classic/aero/browser/devtools/font-inspector.css (../shared/devtools/font-inspector.css)
- skin/classic/aero/browser/devtools/computedview.css (devtools/computedview.css)
+ skin/classic/aero/browser/devtools/computedview.css (../shared/devtools/computedview.css)
skin/classic/aero/browser/devtools/arrow-e.png (../shared/devtools/images/arrow-e.png)
skin/classic/aero/browser/devtools/responsiveui-rotate.png (../shared/devtools/responsiveui-rotate.png)
skin/classic/aero/browser/devtools/responsiveui-touch.png (../shared/devtools/responsiveui-touch.png)
diff --git a/configure.in b/configure.in
index 83de417579b4..d0a305a691a8 100644
--- a/configure.in
+++ b/configure.in
@@ -2163,7 +2163,7 @@ ia64*-hpux*)
LIBS="$LIBS kernel32.lib user32.lib gdi32.lib winmm.lib wsock32.lib advapi32.lib secur32.lib netapi32.lib"
MOZ_DEBUG_LDFLAGS='-DEBUG -DEBUGTYPE:CV'
WARNINGS_AS_ERRORS='-WX'
- MOZ_OPTIMIZE_FLAGS='-O1'
+ MOZ_OPTIMIZE_FLAGS='-O1 -Oi'
MOZ_FIX_LINK_PATHS=
MOZ_COMPONENT_NSPR_LIBS='$(NSPR_LIBS)'
LDFLAGS="$LDFLAGS -LARGEADDRESSAWARE -NXCOMPAT"
diff --git a/content/base/src/contentSecurityPolicy.js b/content/base/src/contentSecurityPolicy.js
index 99cf6197456c..ff0702580225 100644
--- a/content/base/src/contentSecurityPolicy.js
+++ b/content/base/src/contentSecurityPolicy.js
@@ -639,9 +639,11 @@ ContentSecurityPolicy.prototype = {
}
var violationMessage = null;
if (blockedUri["asciiSpec"]) {
- violationMessage = CSPLocalizer.getFormatStr("CSPViolationWithURI", [violatedDirective, blockedUri.asciiSpec]);
+ let localizeString = policy._reportOnlyMode ? "CSPROViolationWithURI" : "CSPViolationWithURI";
+ violationMessage = CSPLocalizer.getFormatStr(localizeString, [violatedDirective, blockedUri.asciiSpec]);
} else {
- violationMessage = CSPLocalizer.getFormatStr("CSPViolation", [violatedDirective]);
+ let localizeString = policy._reportOnlyMode ? "CSPROViolation" : "CSPViolation";
+ violationMessage = CSPLocalizer.getFormatStr(localizeString, [violatedDirective]);
}
policy.log(WARN_FLAG, violationMessage,
(aSourceFile) ? aSourceFile : null,
diff --git a/content/html/content/src/HTMLFormElement.cpp b/content/html/content/src/HTMLFormElement.cpp
index c8fd4c3c8844..a5dafe06c942 100644
--- a/content/html/content/src/HTMLFormElement.cpp
+++ b/content/html/content/src/HTMLFormElement.cpp
@@ -12,6 +12,7 @@
#include "mozilla/EventStates.h"
#include "mozilla/dom/HTMLFormControlsCollection.h"
#include "mozilla/dom/HTMLFormElementBinding.h"
+#include "mozilla/Move.h"
#include "nsIHTMLDocument.h"
#include "nsGkAtoms.h"
#include "nsStyleConsts.h"
@@ -1511,7 +1512,7 @@ HTMLFormElement::FlushPendingSubmission()
if (mPendingSubmission) {
// Transfer owning reference so that the submissioin doesn't get deleted
// if we reenter
- nsAutoPtr submission = mPendingSubmission;
+ nsAutoPtr submission = Move(mPendingSubmission);
SubmitSubmission(submission);
}
diff --git a/content/media/webrtc/LoadManagerFactory.cpp b/content/media/webrtc/LoadManagerFactory.cpp
index abaeb875f922..c49ecc226a2a 100644
--- a/content/media/webrtc/LoadManagerFactory.cpp
+++ b/content/media/webrtc/LoadManagerFactory.cpp
@@ -15,7 +15,6 @@ LoadManager* LoadManagerBuild(void)
{
MOZ_ASSERT(NS_IsMainThread());
-#if defined(ANDROID) || defined(LINUX)
int loadMeasurementInterval =
mozilla::Preferences::GetInt("media.navigator.load_adapt.measure_interval", 1000);
int averagingSeconds =
@@ -29,10 +28,6 @@ LoadManager* LoadManagerBuild(void)
averagingSeconds,
highLoadThreshold,
lowLoadThreshold);
-#else
- // LoadManager not implemented on this platform.
- return nullptr;
-#endif
}
void LoadManagerDestroy(mozilla::LoadManager* aLoadManager)
diff --git a/content/media/webrtc/LoadMonitor.cpp b/content/media/webrtc/LoadMonitor.cpp
index 16dfd3f7c4e1..4fe128b0d0ab 100644
--- a/content/media/webrtc/LoadMonitor.cpp
+++ b/content/media/webrtc/LoadMonitor.cpp
@@ -29,6 +29,19 @@
#include
#endif
+#ifdef XP_MACOSX
+#include
+#include
+#include
+#include
+#endif
+
+#ifdef XP_WIN
+#include
+#include
+#pragma comment(lib, "pdh.lib")
+#endif
+
// NSPR_LOG_MODULES=LoadManager:5
#undef LOG
#undef LOG_ENABLED
@@ -141,6 +154,109 @@ void LoadMonitor::Shutdown()
}
}
+#ifdef XP_WIN
+static LPCTSTR TotalCounterPath = _T("\\Processor(_Total)\\% Processor Time");
+
+class WinProcMon
+{
+public:
+ WinProcMon():
+ mQuery(0), mCounter(0) {};
+ ~WinProcMon();
+ nsresult Init();
+ nsresult QuerySystemLoad(float* load_percent);
+ static const uint64_t TicksPerSec = 10000000; //100nsec tick (10MHz)
+private:
+ PDH_HQUERY mQuery;
+ PDH_HCOUNTER mCounter;
+};
+
+WinProcMon::~WinProcMon()
+{
+ if (mQuery != 0) {
+ PdhCloseQuery(mQuery);
+ mQuery = 0;
+ }
+}
+
+nsresult
+WinProcMon::Init()
+{
+ PDH_HQUERY query;
+ PDH_HCOUNTER counter;
+
+ // Get a query handle to the Performance Data Helper
+ PDH_STATUS status = PdhOpenQuery(
+ NULL, // No log file name: use real-time source
+ 0, // zero out user data token: unsued
+ &query);
+
+ if (status != ERROR_SUCCESS) {
+ LOG(("PdhOpenQuery error = %X", status));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Add a pre-defined high performance counter to the query.
+ // This one is for the total CPU usage.
+ status = PdhAddCounter(query, TotalCounterPath, 0, &counter);
+
+ if (status != ERROR_SUCCESS) {
+ PdhCloseQuery(query);
+ LOG(("PdhAddCounter (_Total) error = %X", status));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Need to make an initial query call to set up data capture.
+ status = PdhCollectQueryData(query);
+
+ if (status != ERROR_SUCCESS) {
+ PdhCloseQuery(query);
+ LOG(("PdhCollectQueryData (init) error = %X", status));
+ return NS_ERROR_FAILURE;
+ }
+
+ mQuery = query;
+ mCounter = counter;
+ return NS_OK;
+}
+
+nsresult WinProcMon::QuerySystemLoad(float* load_percent)
+{
+ *load_percent = 0;
+
+ if (mQuery == 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Update all counters associated with this query object.
+ PDH_STATUS status = PdhCollectQueryData(mQuery);
+
+ if (status != ERROR_SUCCESS) {
+ LOG(("PdhCollectQueryData error = %X", status));
+ return NS_ERROR_FAILURE;
+ }
+
+ PDH_FMT_COUNTERVALUE counter;
+ // maximum is 100% regardless of CPU core count.
+ status = PdhGetFormattedCounterValue(
+ mCounter,
+ PDH_FMT_DOUBLE,
+ (LPDWORD)NULL,
+ &counter);
+
+ if (ERROR_SUCCESS != status ||
+ // There are multiple success return values.
+ !IsSuccessSeverity(counter.CStatus)) {
+ LOG(("PdhGetFormattedCounterValue error"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // The result is a percent value, reduce to match expected scale.
+ *load_percent = (float)(counter.doubleValue / 100.0f);
+ return NS_OK;
+}
+#endif
+
class LoadStats
{
public:
@@ -160,7 +276,8 @@ class LoadInfo : public mozilla::RefCounted
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(LoadInfo)
- LoadInfo(int aLoadUpdateInterval);
+ LoadInfo(): mLoadUpdateInterval(0) {};
+ nsresult Init(int aLoadUpdateInterval);
double GetSystemLoad() { return mSystemLoad.GetLoad(); };
double GetProcessLoad() { return mProcessLoad.GetLoad(); };
nsresult UpdateSystemLoad();
@@ -171,17 +288,29 @@ private:
uint64_t current_total_times,
uint64_t current_cpu_times,
LoadStats* loadStat);
+#ifdef XP_WIN
+ WinProcMon mSysMon;
+ HANDLE mProcHandle;
+ int mNumProcessors;
+#endif
LoadStats mSystemLoad;
LoadStats mProcessLoad;
uint64_t mTicksPerInterval;
int mLoadUpdateInterval;
};
-LoadInfo::LoadInfo(int aLoadUpdateInterval)
- : mLoadUpdateInterval(aLoadUpdateInterval)
+nsresult LoadInfo::Init(int aLoadUpdateInterval)
{
-#if defined(ANDROID) || defined(LINUX)
+ mLoadUpdateInterval = aLoadUpdateInterval;
+#ifdef XP_WIN
+ mTicksPerInterval = (WinProcMon::TicksPerSec /*Hz*/
+ * mLoadUpdateInterval /*msec*/) / 1000 ;
+ mNumProcessors = PR_GetNumberOfProcessors();
+ mProcHandle = GetCurrentProcess();
+ return mSysMon.Init();
+#else
mTicksPerInterval = (sysconf(_SC_CLK_TCK) * mLoadUpdateInterval) / 1000;
+ return NS_OK;
#endif
}
@@ -189,10 +318,9 @@ void LoadInfo::UpdateCpuLoad(uint64_t ticks_per_interval,
uint64_t current_total_times,
uint64_t current_cpu_times,
LoadStats *loadStat) {
-
// Check if we get an inconsistent number of ticks.
if (((current_total_times - loadStat->mPrevTotalTimes)
- > (ticks_per_interval * 10))
+ > (ticks_per_interval * 10))
|| current_total_times < loadStat->mPrevTotalTimes
|| current_cpu_times < loadStat->mPrevCpuTimes) {
// Bug at least on the Nexus 4 and Galaxy S4
@@ -210,7 +338,11 @@ void LoadInfo::UpdateCpuLoad(uint64_t ticks_per_interval,
const uint64_t cpu_diff = current_cpu_times - loadStat->mPrevCpuTimes;
const uint64_t total_diff = current_total_times - loadStat->mPrevTotalTimes;
if (total_diff > 0) {
+#ifdef XP_WIN
+ float result = (float)cpu_diff / (float)total_diff/ (float)mNumProcessors;
+#else
float result = (float)cpu_diff / (float)total_diff;
+#endif
loadStat->mPrevLoad = result;
}
loadStat->mPrevTotalTimes = current_total_times;
@@ -254,6 +386,36 @@ nsresult LoadInfo::UpdateSystemLoad()
cpu_times,
&mSystemLoad);
return NS_OK;
+#elif defined(XP_MACOSX)
+ mach_msg_type_number_t info_cnt = HOST_CPU_LOAD_INFO_COUNT;
+ host_cpu_load_info_data_t load_info;
+ kern_return_t rv = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO,
+ (host_info_t)(&load_info), &info_cnt);
+
+ if (rv != KERN_SUCCESS || info_cnt != HOST_CPU_LOAD_INFO_COUNT) {
+ LOG(("Error from mach/host_statistics call"));
+ return NS_ERROR_FAILURE;
+ }
+
+ const uint64_t cpu_times = load_info.cpu_ticks[CPU_STATE_NICE]
+ + load_info.cpu_ticks[CPU_STATE_SYSTEM]
+ + load_info.cpu_ticks[CPU_STATE_USER];
+ const uint64_t total_times = cpu_times + load_info.cpu_ticks[CPU_STATE_IDLE];
+
+ UpdateCpuLoad(mTicksPerInterval,
+ total_times,
+ cpu_times,
+ &mSystemLoad);
+ return NS_OK;
+#elif defined(XP_WIN)
+ float load;
+ nsresult rv = mSysMon.QuerySystemLoad(&load);
+
+ if (rv == NS_OK) {
+ mSystemLoad.mPrevLoad = load;
+ }
+
+ return rv;
#else
// Not implemented
return NS_OK;
@@ -261,7 +423,7 @@ nsresult LoadInfo::UpdateSystemLoad()
}
nsresult LoadInfo::UpdateProcessLoad() {
-#if defined(LINUX) || defined(ANDROID)
+#if defined(LINUX) || defined(ANDROID) || defined(XP_MACOSX)
struct timeval tv;
gettimeofday(&tv, nullptr);
const uint64_t total_times = tv.tv_sec * PR_USEC_PER_SEC + tv.tv_usec;
@@ -280,7 +442,29 @@ nsresult LoadInfo::UpdateProcessLoad() {
total_times,
cpu_times,
&mProcessLoad);
-#endif // defined(LINUX) || defined(ANDROID)
+#elif defined(XP_WIN)
+ FILETIME clk_time, sys_time, user_time;
+ uint64_t total_times, cpu_times;
+
+ GetSystemTimeAsFileTime(&clk_time);
+ total_times = (((uint64_t)clk_time.dwHighDateTime) << 32)
+ + (uint64_t)clk_time.dwLowDateTime;
+ BOOL ok = GetProcessTimes(mProcHandle, &clk_time, &clk_time, &sys_time, &user_time);
+
+ if (ok == 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ cpu_times = (((uint64_t)sys_time.dwHighDateTime
+ + (uint64_t)user_time.dwHighDateTime) << 32)
+ + (uint64_t)sys_time.dwLowDateTime
+ + (uint64_t)user_time.dwLowDateTime;
+
+ UpdateCpuLoad(mTicksPerInterval,
+ total_times,
+ cpu_times,
+ &mProcessLoad);
+#endif
return NS_OK;
}
@@ -288,12 +472,12 @@ class LoadInfoCollectRunner : public nsRunnable
{
public:
LoadInfoCollectRunner(nsRefPtr loadMonitor,
- int aLoadUpdateInterval)
- : mLoadUpdateInterval(aLoadUpdateInterval),
+ RefPtr loadInfo)
+ : mLoadUpdateInterval(loadMonitor->mLoadUpdateInterval),
mLoadNoiseCounter(0)
{
mLoadMonitor = loadMonitor;
- mLoadInfo = new LoadInfo(mLoadUpdateInterval);
+ mLoadInfo = loadInfo;
}
NS_IMETHOD Run()
@@ -304,6 +488,7 @@ public:
mLoadInfo->UpdateProcessLoad();
float sysLoad = mLoadInfo->GetSystemLoad();
float procLoad = mLoadInfo->GetProcessLoad();
+
if ((++mLoadNoiseCounter % (LOG_MANY_ENABLED() ? 1 : 10)) == 0) {
LOG(("System Load: %f Process Load: %f", sysLoad, procLoad));
mLoadNoiseCounter = 0;
@@ -362,16 +547,22 @@ LoadMonitor::Init(nsRefPtr &self)
{
LOG(("Initializing LoadMonitor"));
-#if defined(ANDROID) || defined(LINUX)
+ RefPtr load_info = new LoadInfo();
+ nsresult rv = load_info->Init(mLoadUpdateInterval);
+
+ if (NS_FAILED(rv)) {
+ LOG(("LoadInfo::Init error"));
+ return rv;
+ }
+
nsRefPtr addObsRunner = new LoadMonitorAddObserver(self);
NS_DispatchToMainThread(addObsRunner, NS_DISPATCH_NORMAL);
NS_NewNamedThread("Sys Load Info", getter_AddRefs(mLoadInfoThread));
nsRefPtr runner =
- new LoadInfoCollectRunner(self, mLoadUpdateInterval);
+ new LoadInfoCollectRunner(self, load_info);
mLoadInfoThread->Dispatch(runner, NS_DISPATCH_NORMAL);
-#endif
return NS_OK;
}
diff --git a/content/media/webrtc/moz.build b/content/media/webrtc/moz.build
index ac7f09ddc8a0..f1d82ed99f4b 100644
--- a/content/media/webrtc/moz.build
+++ b/content/media/webrtc/moz.build
@@ -19,16 +19,13 @@ if CONFIG['MOZ_WEBRTC']:
'LoadMonitor.h',
'MediaEngineWebRTC.h']
UNIFIED_SOURCES += [
+ 'LoadManager.cpp',
'LoadManagerFactory.cpp',
+ 'LoadMonitor.cpp',
'MediaEngineTabVideoSource.cpp',
'MediaEngineWebRTCAudio.cpp',
'MediaEngineWebRTCVideo.cpp',
]
- if CONFIG['OS_ARCH'] == 'Android' or CONFIG['OS_ARCH'] == 'Linux':
- UNIFIED_SOURCES += [
- 'LoadManager.cpp',
- 'LoadMonitor.cpp',
- ]
# MediaEngineWebRTC.cpp needs to be built separately.
SOURCES += [
'MediaEngineWebRTC.cpp',
diff --git a/dom/inputmethod/MozKeyboard.js b/dom/inputmethod/MozKeyboard.js
index 1435b11dcec1..ed1d348dc8fb 100644
--- a/dom/inputmethod/MozKeyboard.js
+++ b/dom/inputmethod/MozKeyboard.js
@@ -32,7 +32,7 @@ let WindowMap = {
if (!this._map || !win) {
return false;
}
- return this._map.get(win, false);
+ return this._map.get(win) || false;
},
/*
diff --git a/dom/locales/en-US/chrome/security/csp.properties b/dom/locales/en-US/chrome/security/csp.properties
index 89242b6447dc..7c96090475e5 100644
--- a/dom/locales/en-US/chrome/security/csp.properties
+++ b/dom/locales/en-US/chrome/security/csp.properties
@@ -10,6 +10,13 @@ CSPViolation = The page's settings blocked the loading of a resource: %1$S
# %1$S is the directive that has been violated.
# %2$S is the URI of the resource which violated the directive.
CSPViolationWithURI = The page's settings blocked the loading of a resource at %2$S ("%1$S").
+# LOCALIZATION NOTE (CSPROViolation):
+# %1$S is the reason why the resource has not been loaded.
+CSPROViolation = A violation occurred for a report-only CSP policy ("%1$S"). The behavior was allowed, and a CSP report was sent.
+# LOCALIZATION NOTE (CSPROViolationWithURI):
+# %1$S is the directive that has been violated.
+# %2$S is the URI of the resource which violated the directive.
+CSPROViolationWithURI = The page's settings observed the loading of a resource at %2$S ("%1$S"). A CSP report is being sent.
# LOCALIZATION NOTE (triedToSendReport):
# %1$S is the URI we attempted to send a report to.
triedToSendReport = Tried to send report to invalid URI: "%1$S"
diff --git a/dom/plugins/test/mochitest/test_CrashService_crash.html b/dom/plugins/test/mochitest/test_CrashService_crash.html
index 0e6fc0ae0465..81da2dca24e7 100644
--- a/dom/plugins/test/mochitest/test_CrashService_crash.html
+++ b/dom/plugins/test/mochitest/test_CrashService_crash.html
@@ -6,6 +6,7 @@