зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c.
This commit is contained in:
Коммит
1aed2f609e
|
@ -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);
|
||||
|
|
|
@ -566,6 +566,8 @@ var FullScreen = {
|
|||
navbar.appendChild(fullscreenctls);
|
||||
}
|
||||
fullscreenctls.hidden = aShow;
|
||||
|
||||
ToolbarIconColor.inferFromText();
|
||||
}
|
||||
};
|
||||
XPCOMUtils.defineLazyGetter(FullScreen, "useLionFullScreen", function() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -899,10 +899,10 @@
|
|||
<toolbarbutton id="social-share-button"
|
||||
class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
hidden="true"
|
||||
overflows="false"
|
||||
label="&sharePageCmd.label;"
|
||||
tooltiptext="&sharePageCmd.label;"
|
||||
cui-areatype="toolbar"
|
||||
removable="true"
|
||||
command="Social:SharePage"/>
|
||||
</hbox>
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 <TranslatedText> 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 =
|
||||
'<TranslateArrayRequest>' +
|
||||
'<AppId/>' +
|
||||
'<From>' + this.sourceLanguage + '</From>' +
|
||||
'<Options>' +
|
||||
'<ContentType xmlns="http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2">text/html</ContentType>' +
|
||||
'<ReservedFlags xmlns="http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2" />' +
|
||||
'</Options>' +
|
||||
'<Texts xmlns:s="http://schemas.microsoft.com/2003/10/Serialization/Arrays">';
|
||||
|
||||
for (let [, text] of this.translationData) {
|
||||
requestString += '<s:string>' + text + '</s:string>';
|
||||
}
|
||||
|
||||
requestString += '</Texts>' +
|
||||
'<To>' + this.targetLanguage + '</To>' +
|
||||
'</TranslateArrayRequest>';
|
||||
|
||||
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");
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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 += '<br/>';
|
||||
}
|
||||
}
|
||||
|
||||
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: <div id="n1">Welcome to <b id="n2">Mozilla's</b> website</div>
|
||||
* Portuguese: <div id="n1">Bem vindo a pagina <b id="n2">da Mozilla</b></div>
|
||||
*
|
||||
* 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:
|
||||
*
|
||||
* <div id="n1">Hello <b id="n2">World</b> of Mozilla.</div>
|
||||
*
|
||||
* 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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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']
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -134,4 +134,101 @@ selector in floating-scrollbar-light.css across all platforms. */
|
|||
.CodeMirror-foldgutter-folded:after {
|
||||
font-size: 120%;
|
||||
content: "\25B8";
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
})();
|
|
@ -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 + ": <input type=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); };
|
||||
}
|
||||
})();
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -119,7 +119,7 @@
|
|||
<popupset>
|
||||
|
||||
<!-- App panel -->
|
||||
<panel id="project-panel" type="arrow" position="bottomcenter topleft" consumeoutsideclicks="true">
|
||||
<panel id="project-panel" type="arrow" position="bottomcenter topleft" consumeoutsideclicks="true" animate="false">
|
||||
<vbox flex="1">
|
||||
<toolbarbutton class="panel-item project-panel-item-newapp" command="cmd_newApp"/>
|
||||
<toolbarbutton class="panel-item project-panel-item-openpackaged" command="cmd_importPackagedApp"/>
|
||||
|
@ -132,7 +132,7 @@
|
|||
</panel>
|
||||
|
||||
<!-- Runtime panel -->
|
||||
<panel id="runtime-panel" type="arrow" position="bottomcenter topright" consumeoutsideclicks="true">
|
||||
<panel id="runtime-panel" type="arrow" position="bottomcenter topright" consumeoutsideclicks="true" animate="false">
|
||||
<vbox flex="1">
|
||||
<label class="panel-header">&runtimePanel_USBDevices;</label>
|
||||
<vbox id="runtime-panel-usbruntime"></vbox>
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
Двоичные данные
browser/themes/shared/devtools/images/itemArrow-dark-ltr.png
Двоичные данные
browser/themes/shared/devtools/images/itemArrow-dark-ltr.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 2.8 KiB |
|
@ -0,0 +1,4 @@
|
|||
<svg width="7" xmlns="http://www.w3.org/2000/svg" height="12" viewBox="0 0 7 12" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 7 12">
|
||||
<path fill="#181D20" d="M7,11.6 7,.4 1.5,6z"/>
|
||||
<path fill="#000" d="M7,0 6,0 0,6 6,12 7,12 7,11.6 1.5,6 7,.4z"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 278 B |
Двоичные данные
browser/themes/shared/devtools/images/itemArrow-dark-rtl.png
Двоичные данные
browser/themes/shared/devtools/images/itemArrow-dark-rtl.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 2.8 KiB |
|
@ -0,0 +1,4 @@
|
|||
<svg width="7" xmlns="http://www.w3.org/2000/svg" height="12" viewBox="0 0 7 12" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 7 12">
|
||||
<path fill="#181D20" d="M0,11.6 0,.4 5.5,6z"/>
|
||||
<path fill="#000" d="M1,0 0,0 0,.4 5.5,6 0,11.6 0,12 1,12 7,6z"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 280 B |
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<body>
|
||||
<script class="testbody" type="application/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestCompleteLog();
|
||||
setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
|
||||
|
||||
window.frameLoaded = function frameLoaded_toCrash() {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<body>
|
||||
<script class="testbody" type="application/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestCompleteLog();
|
||||
setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
|
||||
|
||||
window.frameLoaded = function frameLoaded_toCrash() {
|
||||
|
|
|
@ -466,7 +466,7 @@ pref("breakpad.reportURL", "https://crash-stats.mozilla.com/report/index/");
|
|||
pref("app.support.baseURL", "http://support.mozilla.org/1/mobile/%VERSION%/%OS%/%LOCALE%/");
|
||||
// Used to submit data to input from about:feedback
|
||||
pref("app.feedback.postURL", "https://input.mozilla.org/%LOCALE%/feedback");
|
||||
pref("app.privacyURL", "https://www.mozilla.org/legal/privacy/firefox.html");
|
||||
pref("app.privacyURL", "https://www.mozilla.org/privacy/firefox/");
|
||||
pref("app.creditsURL", "http://www.mozilla.org/credits/");
|
||||
pref("app.channelURL", "http://www.mozilla.org/%LOCALE%/firefox/channel/");
|
||||
#if MOZ_UPDATE_CHANNEL == aurora
|
||||
|
|
|
@ -6,11 +6,14 @@ package org.mozilla.gecko;
|
|||
|
||||
import org.mozilla.gecko.widget.GeckoPopupMenu;
|
||||
|
||||
import android.view.Gravity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
class ActionModeCompat implements GeckoPopupMenu.OnMenuItemClickListener,
|
||||
GeckoPopupMenu.OnMenuItemLongClickListener,
|
||||
View.OnClickListener {
|
||||
private final String LOGTAG = "GeckoActionModeCompat";
|
||||
|
||||
|
@ -93,9 +96,31 @@ class ActionModeCompat implements GeckoPopupMenu.OnMenuItemClickListener,
|
|||
return false;
|
||||
}
|
||||
|
||||
/* GeckoPopupMenu.onMenuItemLongClickListener */
|
||||
@Override
|
||||
public boolean onMenuItemLongClick(MenuItem item) {
|
||||
showTooltip(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* View.OnClickListener*/
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mPresenter.endActionModeCompat();
|
||||
}
|
||||
|
||||
private void showTooltip(MenuItem item) {
|
||||
// Computes the tooltip toast screen position (shown when long-tapping the menu item) with regards to the
|
||||
// menu item's position (i.e below the item and slightly to the left)
|
||||
int[] location = new int[2];
|
||||
final View view = item.getActionView();
|
||||
view.getLocationOnScreen(location);
|
||||
|
||||
int xOffset = location[0] - view.getWidth();
|
||||
int yOffset = location[1] + view.getHeight() / 2;
|
||||
|
||||
Toast toast = Toast.makeText(view.getContext(), item.getTitle(), Toast.LENGTH_SHORT);
|
||||
toast.setGravity(Gravity.TOP|Gravity.LEFT, xOffset, yOffset);
|
||||
toast.show();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ class ActionModeCompatView extends LinearLayout implements GeckoMenu.ActionItemB
|
|||
public void initForMode(final ActionModeCompat mode) {
|
||||
mTitleView.setOnClickListener(mode);
|
||||
mPopupMenu.setOnMenuItemClickListener(mode);
|
||||
mPopupMenu.setOnMenuItemLongClickListener(mode);
|
||||
}
|
||||
|
||||
public CharSequence getTitle() {
|
||||
|
|
|
@ -16,10 +16,11 @@ public class CustomEditText extends ThemedEditText {
|
|||
private OnKeyPreImeListener mOnKeyPreImeListener;
|
||||
private OnSelectionChangedListener mOnSelectionChangedListener;
|
||||
private OnWindowFocusChangeListener mOnWindowFocusChangeListener;
|
||||
private int mHighlightColor;
|
||||
|
||||
public CustomEditText(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mOnKeyPreImeListener = null;
|
||||
setPrivateMode(false); // Initialize mHighlightColor.
|
||||
}
|
||||
|
||||
public interface OnKeyPreImeListener {
|
||||
|
@ -69,12 +70,19 @@ public class CustomEditText extends ThemedEditText {
|
|||
mOnWindowFocusChangeListener.onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
|
||||
// Provide a getHighlightColor implementation for API level < 16.
|
||||
@Override
|
||||
public int getHighlightColor() {
|
||||
return mHighlightColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrivateMode(boolean isPrivate) {
|
||||
super.setPrivateMode(isPrivate);
|
||||
|
||||
mHighlightColor = getContext().getResources().getColor(isPrivate
|
||||
? R.color.url_bar_text_highlight_pb : R.color.url_bar_text_highlight);
|
||||
// android:textColorHighlight cannot support a ColorStateList.
|
||||
int colorId = isPrivate ? R.color.url_bar_text_highlight_pb : R.color.url_bar_text_highlight;
|
||||
setHighlightColor(getContext().getResources().getColor(colorId));
|
||||
setHighlightColor(mHighlightColor);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ import java.io.File;
|
|||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Queue;
|
||||
|
||||
class FilePickerResultHandler implements ActivityResultHandler {
|
||||
private static final String LOGTAG = "GeckoFilePickerResultHandler";
|
||||
|
@ -144,10 +143,26 @@ class FilePickerResultHandler implements ActivityResultHandler {
|
|||
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||
if (cursor.moveToFirst()) {
|
||||
String res = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));
|
||||
|
||||
// Some pickers (the KitKat Documents one for instance) won't return a temporary file here.
|
||||
// Fall back to the normal FileLoader if we didn't find anything.
|
||||
if (TextUtils.isEmpty(res)) {
|
||||
tryFileLoaderCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
sendResult(res);
|
||||
} else {
|
||||
tryFileLoaderCallback();
|
||||
}
|
||||
}
|
||||
|
||||
private void tryFileLoaderCallback() {
|
||||
final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
|
||||
final LoaderManager lm = fa.getSupportLoaderManager();
|
||||
lm.initLoader(uri.hashCode(), null, new FileLoaderCallbacks(uri));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) { }
|
||||
}
|
||||
|
|
|
@ -353,10 +353,15 @@ public abstract class GeckoApp
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(MenuItem item) {
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
return onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemLongClick(MenuItem item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openMenu() {
|
||||
openOptionsMenu();
|
||||
|
|
|
@ -349,8 +349,10 @@ public class GeckoAppShell
|
|||
}
|
||||
});
|
||||
|
||||
if (!AppConstants.MOZILLA_OFFICIAL) {
|
||||
Log.d(LOGTAG, "GeckoLoader.nativeRun " + combinedArgs);
|
||||
}
|
||||
// and go
|
||||
Log.d(LOGTAG, "GeckoLoader.nativeRun " + combinedArgs);
|
||||
GeckoLoader.nativeRun(combinedArgs);
|
||||
|
||||
// Remove pumpMessageLoop() idle handler
|
||||
|
|
|
@ -169,8 +169,10 @@ public class GeckoThread extends Thread implements GeckoEventListener {
|
|||
String args = addCustomProfileArg(mArgs);
|
||||
String type = getTypeFromAction(mAction);
|
||||
|
||||
if (!AppConstants.MOZILLA_OFFICIAL) {
|
||||
Log.i(LOGTAG, "RunGecko - args = " + args);
|
||||
}
|
||||
// and then fire us up
|
||||
Log.i(LOGTAG, "RunGecko - args = " + args);
|
||||
GeckoAppShell.runGecko(path, args, mUri, type);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.ActionProvider;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
|
@ -46,11 +45,14 @@ public class GeckoMenu extends ListView
|
|||
private static final AssertBehavior THREAD_ASSERT_BEHAVIOR = AppConstants.RELEASE_BUILD ? AssertBehavior.NONE : AssertBehavior.THROW;
|
||||
|
||||
/*
|
||||
* A callback for a menu item selected event.
|
||||
* A callback for a menu item click/long click event.
|
||||
*/
|
||||
public static interface Callback {
|
||||
// Called when a menu item is selected, with the actual menu item as the argument.
|
||||
public boolean onMenuItemSelected(MenuItem item);
|
||||
// Called when a menu item is clicked, with the actual menu item as the argument.
|
||||
public boolean onMenuItemClick(MenuItem item);
|
||||
|
||||
// Called when a menu item is long-clicked, with the actual menu item as the argument.
|
||||
public boolean onMenuItemLongClick(MenuItem item);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -222,6 +224,13 @@ public class GeckoMenu extends ListView
|
|||
handleMenuItemClick(menuItem);
|
||||
}
|
||||
});
|
||||
((MenuItemActionBar) actionView).setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
handleMenuItemLongClick(menuItem);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} else if (actionView instanceof MenuItemActionView) {
|
||||
((MenuItemActionView) actionView).setMenuItemClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
|
@ -229,6 +238,13 @@ public class GeckoMenu extends ListView
|
|||
handleMenuItemClick(menuItem);
|
||||
}
|
||||
});
|
||||
((MenuItemActionView) actionView).setMenuItemLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
handleMenuItemLongClick(menuItem);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return added;
|
||||
|
@ -556,7 +572,17 @@ public class GeckoMenu extends ListView
|
|||
showMenu(subMenu);
|
||||
} else {
|
||||
close();
|
||||
mCallback.onMenuItemSelected(item);
|
||||
mCallback.onMenuItemClick(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMenuItemLongClick(GeckoMenuItem item) {
|
||||
if(!item.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(mCallback != null) {
|
||||
mCallback.onMenuItemLongClick(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import org.mozilla.gecko.R;
|
|||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -103,6 +102,11 @@ public class MenuItemActionView extends LinearLayout
|
|||
mMenuButton.setOnClickListener(listener);
|
||||
}
|
||||
|
||||
public void setMenuItemLongClickListener(View.OnLongClickListener listener) {
|
||||
mMenuItem.setOnLongClickListener(listener);
|
||||
mMenuButton.setOnLongClickListener(listener);
|
||||
}
|
||||
|
||||
public void setActionButtonClickListener(View.OnClickListener listener) {
|
||||
mActionButtonListener = listener;
|
||||
|
||||
|
|
|
@ -48,13 +48,7 @@ let videoDiscoveryTests = [
|
|||
{ id: "with-sources-mp4", source: "http://mochi.test:8888/simple.mp4", text: "video with mp4 extension source child" },
|
||||
{ id: "with-sources-fail", pass: false, text: "video with no mp4 extension source child" },
|
||||
{ id: "with-sources-mimetype", source: "http://mochi.test:8888/simple-video-mp4", text: "video with mp4 mimetype source child" },
|
||||
{ id: "video-overlay", source: "http://mochi.test:8888/simple.mp4", text: "div overlay covering a simple video with mp4 src" },
|
||||
|
||||
{ id: "opt-in-simple-mp4", source: "http://mochi.test:8888/simple.mp4", poster: "http://mochi.test:8888/simple.png", text: "simple video with mp4 src" },
|
||||
{ id: "opt-out-simple-mp4", pass: false, text: "simple video with mp4 src but opt-out" },
|
||||
{ id: "opt-in-simple-fail", pass: false, text: "simple video with opt-in but no mp4 src" },
|
||||
{ id: "opt-in-with-sources-mp4", source: "http://mochi.test:8888/simple.mp4", text: "opt-in video with mp4 extension source child" },
|
||||
{ id: "opt-out-with-sources-mp4", pass: false, text: "video with mp4 extension source child but opt-out" }
|
||||
{ id: "video-overlay", source: "http://mochi.test:8888/simple.mp4", text: "div overlay covering a simple video with mp4 src" }
|
||||
];
|
||||
|
||||
function execute_video_test(test) {
|
||||
|
|
|
@ -56,26 +56,5 @@
|
|||
<video id="video-player" src="/simple.mp4"></video>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PASS: opt-in and src uses a mp4 extension -->
|
||||
<video id="opt-in-simple-mp4" poster="/simple.png" src="/simple.mp4" x-webkit-airplay="allow"></video>
|
||||
|
||||
<!-- FAIL: opt-out and src uses a mp4 extension -->
|
||||
<video id="opt-out-simple-mp4" poster="/simple.png" src="/simple.mp4" x-webkit-airplay="deny"></video>
|
||||
|
||||
<!-- FAIL: opt-in and src uses a ogg extension -->
|
||||
<video id="opt-in-simple-fail" src="/simple.ogg" x-webkit-airplay="allow"></video>
|
||||
|
||||
<!-- PASS: video with opt-in and source list uses a mp4 extension -->
|
||||
<video id="opt-in-with-sources-mp4" x-webkit-airplay="allow">
|
||||
<source src="/simple.ogg">
|
||||
<source src="/simple.mp4">
|
||||
</video>
|
||||
|
||||
<!-- FAIL: video with opt-out and source list uses a mp4 extension -->
|
||||
<video id="opt-out-with-sources-mp4" x-webkit-airplay="deny">
|
||||
<source src="/simple.ogg">
|
||||
<source src="/simple.mp4">
|
||||
</video>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -379,7 +379,7 @@ public class BrowserToolbar extends ThemedRelativeLayout
|
|||
public void onClick(View v) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL,
|
||||
TelemetryContract.Method.ACTIONBAR,
|
||||
Integer.toString(editCancel.getId()));
|
||||
getResources().getResourceEntryName(editCancel.getId()));
|
||||
cancelEdit();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -11,20 +11,29 @@ import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
|
|||
import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
|
||||
import org.mozilla.gecko.CustomEditText;
|
||||
import org.mozilla.gecko.CustomEditText.OnKeyPreImeListener;
|
||||
import org.mozilla.gecko.InputMethods;
|
||||
import org.mozilla.gecko.util.GamepadUtils;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.text.Editable;
|
||||
import android.text.NoCopySpan;
|
||||
import android.text.Selection;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnKeyListener;
|
||||
import android.view.inputmethod.BaseInputConnection;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputConnectionWrapper;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
/**
|
||||
|
@ -36,6 +45,7 @@ public class ToolbarEditText extends CustomEditText
|
|||
implements AutocompleteHandler {
|
||||
|
||||
private static final String LOGTAG = "GeckoToolbarEditText";
|
||||
private static final NoCopySpan AUTOCOMPLETE_SPAN = new NoCopySpan.Concrete();
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
|
@ -45,9 +55,12 @@ public class ToolbarEditText extends CustomEditText
|
|||
|
||||
// The previous autocomplete result returned to us
|
||||
private String mAutoCompleteResult = "";
|
||||
|
||||
// The user typed part of the autocomplete result
|
||||
private String mAutoCompletePrefix = null;
|
||||
// Length of the user-typed portion of the result
|
||||
private int mAutoCompletePrefixLength;
|
||||
// If text change is due to us setting autocomplete
|
||||
private boolean mSettingAutoComplete;
|
||||
// Spans used for marking the autocomplete text
|
||||
private Object[] mAutoCompleteSpans;
|
||||
|
||||
public ToolbarEditText(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
@ -70,6 +83,7 @@ public class ToolbarEditText extends CustomEditText
|
|||
public void onAttachedToWindow() {
|
||||
setOnKeyListener(new KeyListener());
|
||||
setOnKeyPreImeListener(new KeyPreImeListener());
|
||||
setOnSelectionChangedListener(new SelectionChangeListener());
|
||||
addTextChangedListener(new TextChangeListener());
|
||||
}
|
||||
|
||||
|
@ -82,8 +96,9 @@ public class ToolbarEditText extends CustomEditText
|
|||
return;
|
||||
}
|
||||
|
||||
InputMethodManager imm = (InputMethodManager)
|
||||
mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
removeAutocomplete(getText());
|
||||
|
||||
final InputMethodManager imm = InputMethods.getInputMethodManager(mContext);
|
||||
try {
|
||||
imm.restartInput(this);
|
||||
imm.hideSoftInputFromWindow(getWindowToken(), 0);
|
||||
|
@ -93,33 +108,216 @@ public class ToolbarEditText extends CustomEditText
|
|||
}
|
||||
}
|
||||
|
||||
// Return early if we're backspacing through the string, or
|
||||
// have no autocomplete results
|
||||
@Override
|
||||
public final void onAutocomplete(final String result) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
/**
|
||||
* Mark the start of autocomplete changes so our text change
|
||||
* listener does not react to changes in autocomplete text
|
||||
*/
|
||||
private void beginSettingAutocomplete() {
|
||||
beginBatchEdit();
|
||||
mSettingAutoComplete = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the end of autocomplete changes
|
||||
*/
|
||||
private void endSettingAutocomplete() {
|
||||
mSettingAutoComplete = false;
|
||||
endBatchEdit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset autocomplete states to their initial values
|
||||
*/
|
||||
private void resetAutocompleteState() {
|
||||
mAutoCompleteSpans = new Object[] {
|
||||
// Span to mark the autocomplete text
|
||||
AUTOCOMPLETE_SPAN,
|
||||
// Span to change the autocomplete text color
|
||||
new BackgroundColorSpan(getHighlightColor())
|
||||
};
|
||||
|
||||
mAutoCompleteResult = "";
|
||||
mAutoCompletePrefixLength = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the portion of text that is not marked as autocomplete text.
|
||||
*
|
||||
* @param text Current text content that may include autocomplete text
|
||||
*/
|
||||
private static String getNonAutocompleteText(final Editable text) {
|
||||
final int start = text.getSpanStart(AUTOCOMPLETE_SPAN);
|
||||
if (start < 0) {
|
||||
// No autocomplete text; return the whole string.
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
final String text = getText().toString();
|
||||
// Only return the portion that's not autocomplete text
|
||||
return TextUtils.substring(text, 0, start);
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
/**
|
||||
* Remove any autocomplete text
|
||||
*
|
||||
* @param text Current text content that may include autocomplete text
|
||||
*/
|
||||
private boolean removeAutocomplete(final Editable text) {
|
||||
final int start = text.getSpanStart(AUTOCOMPLETE_SPAN);
|
||||
if (start < 0) {
|
||||
// No autocomplete text
|
||||
return false;
|
||||
}
|
||||
|
||||
beginSettingAutocomplete();
|
||||
|
||||
// When we call delete() here, the autocomplete spans we set are removed as well.
|
||||
text.delete(start, text.length());
|
||||
|
||||
// Keep mAutoCompletePrefixLength the same because the prefix has not changed.
|
||||
// Clear mAutoCompleteResult to make sure we get fresh autocomplete text next time.
|
||||
mAutoCompleteResult = "";
|
||||
|
||||
// Reshow the cursor.
|
||||
setCursorVisible(true);
|
||||
|
||||
endSettingAutocomplete();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert any autocomplete text to regular text
|
||||
*
|
||||
* @param text Current text content that may include autocomplete text
|
||||
*/
|
||||
private boolean commitAutocomplete(final Editable text) {
|
||||
final int start = text.getSpanStart(AUTOCOMPLETE_SPAN);
|
||||
if (start < 0) {
|
||||
// No autocomplete text
|
||||
return false;
|
||||
}
|
||||
|
||||
beginSettingAutocomplete();
|
||||
|
||||
// Remove all spans here to convert from autocomplete text to regular text
|
||||
for (final Object span : mAutoCompleteSpans) {
|
||||
text.removeSpan(span);
|
||||
}
|
||||
|
||||
// Keep mAutoCompleteResult the same because the result has not changed.
|
||||
// Reset mAutoCompletePrefixLength because the prefix now includes the autocomplete text.
|
||||
mAutoCompletePrefixLength = text.length();
|
||||
|
||||
// Reshow the cursor.
|
||||
setCursorVisible(true);
|
||||
|
||||
endSettingAutocomplete();
|
||||
|
||||
// Filter on the new text
|
||||
if (mFilterListener != null) {
|
||||
mFilterListener.onFilter(text.toString(), null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add autocomplete text based on the result URI.
|
||||
*
|
||||
* @param result Result URI to be turned into autocomplete text
|
||||
*/
|
||||
@Override
|
||||
public final void onAutocomplete(final String result) {
|
||||
if (!isEnabled() || result == null) {
|
||||
mAutoCompleteResult = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.startsWith(text) || text.equals(result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Editable text = getText();
|
||||
final int textLength = text.length();
|
||||
final int resultLength = result.length();
|
||||
final int autoCompleteStart = text.getSpanStart(AUTOCOMPLETE_SPAN);
|
||||
mAutoCompleteResult = result;
|
||||
getText().append(result.substring(text.length()));
|
||||
setSelection(text.length(), result.length());
|
||||
}
|
||||
|
||||
private void resetAutocompleteState() {
|
||||
mAutoCompleteResult = "";
|
||||
mAutoCompletePrefix = null;
|
||||
if (autoCompleteStart > -1) {
|
||||
// Autocomplete text already exists; we should replace existing autocomplete text.
|
||||
|
||||
// If the result and the current text don't have the same prefixes,
|
||||
// the result is stale and we should wait for the another result to come in.
|
||||
if (!TextUtils.regionMatches(result, 0, text, 0, autoCompleteStart)) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginSettingAutocomplete();
|
||||
|
||||
// Replace the existing autocomplete text with new one.
|
||||
// replace() preserves the autocomplete spans that we set before.
|
||||
text.replace(autoCompleteStart, textLength, result, autoCompleteStart, resultLength);
|
||||
|
||||
endSettingAutocomplete();
|
||||
|
||||
} else {
|
||||
// No autocomplete text yet; we should add autocomplete text
|
||||
|
||||
// If the result prefix doesn't match the current text,
|
||||
// the result is stale and we should wait for the another result to come in.
|
||||
if (resultLength <= textLength ||
|
||||
!TextUtils.regionMatches(result, 0, text, 0, textLength)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Object[] spans = text.getSpans(textLength, textLength, Object.class);
|
||||
final int[] spanStarts = new int[spans.length];
|
||||
final int[] spanEnds = new int[spans.length];
|
||||
final int[] spanFlags = new int[spans.length];
|
||||
|
||||
// Save selection/composing span bounds so we can restore them later.
|
||||
for (int i = 0; i < spans.length; i++) {
|
||||
final Object span = spans[i];
|
||||
final int spanFlag = text.getSpanFlags(span);
|
||||
|
||||
// We don't care about spans that are not selection or composing spans.
|
||||
// For those spans, spanFlag[i] will be 0 and we don't restore them.
|
||||
if ((spanFlag & Spanned.SPAN_COMPOSING) == 0 &&
|
||||
(span != Selection.SELECTION_START) &&
|
||||
(span != Selection.SELECTION_END)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
spanStarts[i] = text.getSpanStart(span);
|
||||
spanEnds[i] = text.getSpanEnd(span);
|
||||
spanFlags[i] = spanFlag;
|
||||
}
|
||||
|
||||
beginSettingAutocomplete();
|
||||
|
||||
// First add trailing text.
|
||||
text.append(result, textLength, resultLength);
|
||||
|
||||
// Mark added text as autocomplete text.
|
||||
for (final Object span : mAutoCompleteSpans) {
|
||||
text.setSpan(span, textLength, resultLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
// Hide the cursor.
|
||||
setCursorVisible(false);
|
||||
|
||||
// Make sure the autocomplete text is visible. If the autocomplete text is too
|
||||
// long, it would appear the cursor will be scrolled out of view. However, this
|
||||
// is not the case in practice, because EditText still makes sure the cursor is
|
||||
// still in view.
|
||||
bringPointIntoView(resultLength);
|
||||
|
||||
// Restore selection/composing spans.
|
||||
for (int i = 0; i < spans.length; i++) {
|
||||
final int spanFlag = spanFlags[i];
|
||||
if (spanFlag == 0) {
|
||||
// Skip if the span was ignored before.
|
||||
continue;
|
||||
}
|
||||
text.setSpan(spans[i], spanStarts[i], spanEnds[i], spanFlag);
|
||||
}
|
||||
|
||||
endSettingAutocomplete();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasCompositionString(Editable content) {
|
||||
|
@ -137,44 +335,121 @@ public class ToolbarEditText extends CustomEditText
|
|||
return false;
|
||||
}
|
||||
|
||||
private class TextChangeListener implements TextWatcher {
|
||||
/**
|
||||
* Code to handle deleting autocomplete first when backspacing.
|
||||
* If there is no autocomplete text, both removeAutocomplete() and commitAutocomplete()
|
||||
* are no-ops and return false. Therefore we can use them here without checking explicitly
|
||||
* if we have autocomplete text or not.
|
||||
*/
|
||||
@Override
|
||||
public InputConnection onCreateInputConnection(final EditorInfo outAttrs) {
|
||||
final InputConnection ic = super.onCreateInputConnection(outAttrs);
|
||||
if (ic == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new InputConnectionWrapper(ic, false) {
|
||||
@Override
|
||||
public boolean deleteSurroundingText(final int beforeLength, final int afterLength) {
|
||||
if (removeAutocomplete(getText())) {
|
||||
// If we have autocomplete text, the cursor is at the boundary between
|
||||
// regular and autocomplete text. So regardless of which direction we
|
||||
// are deleting, we should delete the autocomplete text first.
|
||||
return false;
|
||||
}
|
||||
return super.deleteSurroundingText(beforeLength, afterLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setComposingText(final CharSequence text, final int newCursorPosition) {
|
||||
final Editable editable = getText();
|
||||
final int composingStart = BaseInputConnection.getComposingSpanStart(editable);
|
||||
final int composingEnd = BaseInputConnection.getComposingSpanEnd(editable);
|
||||
// We only delete the autocomplete text when the user is backspacing,
|
||||
// i.e. when the composing text is getting shorter.
|
||||
if (composingStart >= 0 &&
|
||||
composingEnd >= 0 &&
|
||||
(composingEnd - composingStart) > text.length() &&
|
||||
removeAutocomplete(editable)) {
|
||||
// Make the IME aware that we interrupted the setComposingText call,
|
||||
// by having finishComposingText() send change notifications to the IME.
|
||||
return super.finishComposingText();
|
||||
}
|
||||
return super.setComposingText(text, newCursorPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendKeyEvent(final KeyEvent event) {
|
||||
if ((event.getKeyCode() == KeyEvent.KEYCODE_DEL ||
|
||||
(Build.VERSION.SDK_INT >= 11 &&
|
||||
event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL)) &&
|
||||
removeAutocomplete(getText())) {
|
||||
// Delete autocomplete text when backspacing or forward deleting.
|
||||
return false;
|
||||
}
|
||||
return super.sendKeyEvent(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private class SelectionChangeListener implements OnSelectionChangedListener {
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
if (!isEnabled()) {
|
||||
public void onSelectionChanged(final int selStart, final int selEnd) {
|
||||
// The user has repositioned the cursor somewhere. We need to adjust
|
||||
// the autocomplete text depending on where the new cursor is.
|
||||
|
||||
final Editable text = getText();
|
||||
final int start = text.getSpanStart(AUTOCOMPLETE_SPAN);
|
||||
|
||||
if (start < 0 || (start == selStart && start == selEnd)) {
|
||||
// Do not commit autocomplete text if there is no autocomplete text
|
||||
// or if selection is still at start of autocomplete text
|
||||
return;
|
||||
}
|
||||
|
||||
final String text = s.toString();
|
||||
if (selStart <= start && selEnd <= start) {
|
||||
// The cursor is in user-typed text; remove any autocomplete text.
|
||||
removeAutocomplete(text);
|
||||
} else {
|
||||
// The cursor is in the autocomplete text; commit it so it becomes regular text.
|
||||
commitAutocomplete(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean useHandler = false;
|
||||
boolean reuseAutocomplete = false;
|
||||
|
||||
if (!hasCompositionString(s) && !StringUtils.isSearchQuery(text, false)) {
|
||||
useHandler = true;
|
||||
|
||||
// If you're hitting backspace (the string is getting smaller
|
||||
// or is unchanged), don't autocomplete.
|
||||
if (mAutoCompletePrefix != null && (mAutoCompletePrefix.length() >= text.length())) {
|
||||
useHandler = false;
|
||||
} else if (mAutoCompleteResult != null && mAutoCompleteResult.startsWith(text)) {
|
||||
// If this text already matches our autocomplete text, autocomplete likely
|
||||
// won't change. Just reuse the old autocomplete value.
|
||||
useHandler = false;
|
||||
reuseAutocomplete = true;
|
||||
}
|
||||
private class TextChangeListener implements TextWatcher {
|
||||
@Override
|
||||
public void afterTextChanged(final Editable editable) {
|
||||
if (!isEnabled() || mSettingAutoComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is the autocomplete text being set, don't run the filter.
|
||||
if (TextUtils.isEmpty(mAutoCompleteResult) || !mAutoCompleteResult.equals(text)) {
|
||||
if (mFilterListener != null) {
|
||||
mFilterListener.onFilter(text, useHandler ? ToolbarEditText.this : null);
|
||||
}
|
||||
final String text = getNonAutocompleteText(editable);
|
||||
final int textLength = text.length();
|
||||
boolean doAutocomplete = true;
|
||||
|
||||
mAutoCompletePrefix = text;
|
||||
if (StringUtils.isSearchQuery(text, false)) {
|
||||
doAutocomplete = false;
|
||||
} else if (mAutoCompletePrefixLength > textLength) {
|
||||
// If you're hitting backspace (the string is getting smaller), don't autocomplete
|
||||
doAutocomplete = false;
|
||||
}
|
||||
|
||||
if (reuseAutocomplete) {
|
||||
onAutocomplete(mAutoCompleteResult);
|
||||
}
|
||||
mAutoCompletePrefixLength = textLength;
|
||||
|
||||
if (doAutocomplete && mAutoCompleteResult.startsWith(text)) {
|
||||
// If this text already matches our autocomplete text, autocomplete likely
|
||||
// won't change. Just reuse the old autocomplete value.
|
||||
onAutocomplete(mAutoCompleteResult);
|
||||
doAutocomplete = false;
|
||||
} else {
|
||||
// Otherwise, remove the old autocomplete text
|
||||
// until any new autocomplete text gets added.
|
||||
removeAutocomplete(editable);
|
||||
}
|
||||
|
||||
if (mFilterListener != null) {
|
||||
mFilterListener.onFilter(text, doAutocomplete ? ToolbarEditText.this : null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,11 @@ public class GeckoPopupMenu implements GeckoMenu.Callback,
|
|||
public boolean onMenuItemClick(MenuItem item);
|
||||
}
|
||||
|
||||
// An interface for listeners for menu item long click events.
|
||||
public static interface OnMenuItemLongClickListener {
|
||||
public boolean onMenuItemLongClick(MenuItem item);
|
||||
}
|
||||
|
||||
private View mAnchor;
|
||||
|
||||
private MenuPopup mMenuPopup;
|
||||
|
@ -43,6 +48,7 @@ public class GeckoPopupMenu implements GeckoMenu.Callback,
|
|||
|
||||
private OnDismissListener mDismissListener;
|
||||
private OnMenuItemClickListener mClickListener;
|
||||
private OnMenuItemLongClickListener mLongClickListener;
|
||||
|
||||
public GeckoPopupMenu(Context context) {
|
||||
initialize(context, null);
|
||||
|
@ -117,6 +123,10 @@ public class GeckoPopupMenu implements GeckoMenu.Callback,
|
|||
mClickListener = listener;
|
||||
}
|
||||
|
||||
public void setOnMenuItemLongClickListener(OnMenuItemLongClickListener listener) {
|
||||
mLongClickListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the inflated menu.
|
||||
*/
|
||||
|
@ -138,10 +148,18 @@ public class GeckoPopupMenu implements GeckoMenu.Callback,
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(MenuItem item) {
|
||||
if (mClickListener != null)
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (mClickListener != null) {
|
||||
return mClickListener.onMenuItemClick(item);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemLongClick(MenuItem item) {
|
||||
if (mLongClickListener != null) {
|
||||
return mLongClickListener.onMenuItemLongClick(item);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -176,12 +176,6 @@ var CastingApps = {
|
|||
return null;
|
||||
}
|
||||
|
||||
// Allow websites to opt-out using the Apple airplay attribute
|
||||
// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/AirPlayGuide/OptingInorOutofAirPlay/OptingInorOutofAirPlay.html
|
||||
if (aElement.getAttribute("x-webkit-airplay") === "deny") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Given the hardware support for H264, let's only look for 'mp4' sources
|
||||
function allowableExtension(aURI) {
|
||||
if (aURI && aURI instanceof Ci.nsIURL) {
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
Point 4 discusses web service terms, unbranded builds gets a placeholder (for the vendor to replace) -->
|
||||
<li>&rights.intro-point2-a;<a href="http://www.mozilla.org/foundation/trademarks/policy.html">&rights.intro-point2-b;</a>&rights.intro-point2-c;</li>
|
||||
<li>&rights.intro-point2.5;</li>
|
||||
<li>&rights2.intro-point3a;<a href="https://www.mozilla.org/legal/privacy/firefox.html">&rights2.intro-point3b;</a>&rights.intro-point3c;</li>
|
||||
<li>&rights2.intro-point3a;<a href="https://www.mozilla.org/privacy/firefox/">&rights2.intro-point3b;</a>&rights.intro-point3c;</li>
|
||||
<li>&rights2.intro-point4a;<a href="about:rights#webservices" onclick="showServices();">&rights.intro-point4b;</a>&rights.intro-point4c;</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -450,7 +450,7 @@ SysInfoProvider.prototype = Object.freeze({
|
|||
name: "name",
|
||||
version: "version",
|
||||
arch: "architecture",
|
||||
isWOW64: "isWow64",
|
||||
isWow64: "isWow64",
|
||||
},
|
||||
|
||||
collectConstantData: function () {
|
||||
|
@ -493,6 +493,7 @@ SysInfoProvider.prototype = Object.freeze({
|
|||
// Property is only present on Windows. hasKey() skipping from
|
||||
// above ensures undefined or null doesn't creep in here.
|
||||
value = value ? 1 : 0;
|
||||
method = "setLastNumeric";
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -549,7 +549,7 @@ this.PlacesBackups = {
|
|||
// Since children may be added before parents, we should merge with
|
||||
// the existing object.
|
||||
let original = itemsMap.get(id);
|
||||
for (prop in bookmark) {
|
||||
for (let prop of Object.getOwnPropertyNames(bookmark)) {
|
||||
original[prop] = bookmark[prop];
|
||||
}
|
||||
bookmark = original;
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Checks that backups properly include all of the bookmarks if the hierarchy
|
||||
* in the database is unordered so that a hierarchy is defined before its
|
||||
* ancestor in the bookmarks table.
|
||||
*/
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function() {
|
||||
let bm = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
|
||||
NetUtil.newURI("http://mozilla.org/"),
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"bookmark");
|
||||
let f2 = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId, "f2",
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX);
|
||||
PlacesUtils.bookmarks.moveItem(bm, f2, PlacesUtils.bookmarks.DEFAULT_INDEX);
|
||||
let f1 = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId, "f1",
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX);
|
||||
PlacesUtils.bookmarks.moveItem(f2, f1, PlacesUtils.bookmarks.DEFAULT_INDEX);
|
||||
|
||||
// Create a backup.
|
||||
yield PlacesBackups.create();
|
||||
|
||||
// Remove the bookmarks, then restore the backup.
|
||||
PlacesUtils.bookmarks.removeItem(f1);
|
||||
yield BookmarkJSONUtils.importFromFile((yield PlacesBackups.getMostRecentBackup()), true);
|
||||
|
||||
do_log_info("Checking first level");
|
||||
let root = PlacesUtils.getFolderContents(PlacesUtils.unfiledBookmarksFolderId).root;
|
||||
let level1 = root.getChild(0);
|
||||
do_check_eq(level1.title, "f1");
|
||||
do_log_info("Checking second level");
|
||||
PlacesUtils.asContainer(level1).containerOpen = true
|
||||
let level2 = level1.getChild(0);
|
||||
do_check_eq(level2.title, "f2");
|
||||
do_log_info("Checking bookmark");
|
||||
PlacesUtils.asContainer(level2).containerOpen = true
|
||||
let bookmark = level2.getChild(0);
|
||||
do_check_eq(bookmark.title, "bookmark");
|
||||
level2.containerOpen = false;
|
||||
level1.containerOpen = false;
|
||||
root.containerOpen = false;
|
||||
});
|
|
@ -30,3 +30,4 @@ tail =
|
|||
[test_protectRoots.js]
|
||||
[test_818593-store-backup-metadata.js]
|
||||
[test_818584-discard-duplicate-backups.js]
|
||||
[test_992901-backup-unsorted-hierarchy.js]
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
commit 8885dea3716170ec7f3b213421c330f31cd5ac4b
|
||||
Author: Jed Davis <jld@mozilla.com>
|
||||
Date: Mon May 19 15:41:28 2014 -0700
|
||||
|
||||
Bug 942290: Merge ARM exidx info with DWARF if both are present.
|
||||
|
||||
diff --git a/toolkit/crashreporter/google-breakpad/src/common/arm_ex_reader.cc b/toolkit/crashreporter/google-breakpad/src/common/arm_ex_reader.cc
|
||||
index 5d87ab4..97f99e4 100644
|
||||
--- a/toolkit/crashreporter/google-breakpad/src/common/arm_ex_reader.cc
|
||||
+++ b/toolkit/crashreporter/google-breakpad/src/common/arm_ex_reader.cc
|
||||
@@ -487,14 +487,16 @@ void ExceptionTableInfo::Start()
|
||||
// create CFI entries that Breakpad can use. This can also fail.
|
||||
// First, add a new stack frame entry, into which ExtabEntryDecode
|
||||
// will write the CFI entries.
|
||||
- handler_->AddStackFrame(addr, next_addr - addr);
|
||||
- int ret = ExtabEntryDecode(buf, buf_used);
|
||||
- if (ret < 0) {
|
||||
- handler_->DeleteStackFrame();
|
||||
- BPLOG(INFO) << "ExtabEntryDecode: failed with error code: " << ret;
|
||||
- continue;
|
||||
+ if (!handler_->HasStackFrame(addr, next_addr - addr)) {
|
||||
+ handler_->AddStackFrame(addr, next_addr - addr);
|
||||
+ int ret = ExtabEntryDecode(buf, buf_used);
|
||||
+ if (ret < 0) {
|
||||
+ handler_->DeleteStackFrame();
|
||||
+ BPLOG(INFO) << "ExtabEntryDecode: failed with error code: " << ret;
|
||||
+ continue;
|
||||
+ }
|
||||
+ handler_->SubmitStackFrame();
|
||||
}
|
||||
- handler_->SubmitStackFrame();
|
||||
|
||||
} /* iterating over .exidx */
|
||||
}
|
||||
diff --git a/toolkit/crashreporter/google-breakpad/src/common/arm_ex_to_module.cc b/toolkit/crashreporter/google-breakpad/src/common/arm_ex_to_module.cc
|
||||
index f8535b7..b5e3b06 100644
|
||||
--- a/toolkit/crashreporter/google-breakpad/src/common/arm_ex_to_module.cc
|
||||
+++ b/toolkit/crashreporter/google-breakpad/src/common/arm_ex_to_module.cc
|
||||
@@ -162,6 +162,21 @@ int ARMExToModule::TranslateCmd(const struct extab_data* edata,
|
||||
return ret;
|
||||
}
|
||||
|
||||
+bool ARMExToModule::HasStackFrame(uintptr_t addr, size_t size) {
|
||||
+ // Invariant: the range [addr,covered) is covered by existing stack
|
||||
+ // frame entries.
|
||||
+ uintptr_t covered = addr;
|
||||
+ while (covered < addr + size) {
|
||||
+ const Module::StackFrameEntry *old_entry =
|
||||
+ module_->FindStackFrameEntryByAddress(covered);
|
||||
+ if (!old_entry) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ covered = old_entry->address + old_entry->size;
|
||||
+ }
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
void ARMExToModule::AddStackFrame(uintptr_t addr, size_t size) {
|
||||
stack_frame_entry_ = new Module::StackFrameEntry;
|
||||
stack_frame_entry_->address = addr;
|
||||
diff --git a/toolkit/crashreporter/google-breakpad/src/common/arm_ex_to_module.h b/toolkit/crashreporter/google-breakpad/src/common/arm_ex_to_module.h
|
||||
index 73c936c..9d6a879 100644
|
||||
--- a/toolkit/crashreporter/google-breakpad/src/common/arm_ex_to_module.h
|
||||
+++ b/toolkit/crashreporter/google-breakpad/src/common/arm_ex_to_module.h
|
||||
@@ -108,6 +108,7 @@ class ARMExToModule {
|
||||
ARMExToModule(Module* module)
|
||||
: module_(module) { }
|
||||
~ARMExToModule() { }
|
||||
+ bool HasStackFrame(uintptr_t addr, size_t size);
|
||||
void AddStackFrame(uintptr_t addr, size_t size);
|
||||
int ImproveStackFrame(const struct extab_data* edata);
|
||||
void DeleteStackFrame();
|
||||
diff --git a/toolkit/crashreporter/google-breakpad/src/common/linux/dump_symbols.cc b/toolkit/crashreporter/google-breakpad/src/common/linux/dump_symbols.cc
|
||||
index d59832c..9a54bab 100644
|
||||
--- a/toolkit/crashreporter/google-breakpad/src/common/linux/dump_symbols.cc
|
||||
+++ b/toolkit/crashreporter/google-breakpad/src/common/linux/dump_symbols.cc
|
||||
@@ -695,10 +695,12 @@ bool LoadSymbols(const string& obj_file,
|
||||
FindElfSectionByName<ElfClass>(".ARM.extab", SHT_PROGBITS,
|
||||
sections, names, names_end,
|
||||
elf_header->e_shnum);
|
||||
- // Only load information from this section if there isn't a .debug_info
|
||||
- // section.
|
||||
- if (!found_debug_info_section
|
||||
- && arm_exidx_section && arm_extab_section && symbol_data != NO_CFI) {
|
||||
+ // Load information from these sections even if there is
|
||||
+ // .debug_info, because some functions (e.g., hand-written or
|
||||
+ // script-generated assembly) could have exidx entries but no DWARF.
|
||||
+ // (For functions with both, the DWARF info that has already been
|
||||
+ // parsed will take precedence.)
|
||||
+ if (arm_exidx_section && arm_extab_section && symbol_data != NO_CFI) {
|
||||
info->LoadedSection(".ARM.exidx");
|
||||
info->LoadedSection(".ARM.extab");
|
||||
bool result = LoadARMexidx<ElfClass>(elf_header,
|
|
@ -487,14 +487,16 @@ void ExceptionTableInfo::Start()
|
|||
// create CFI entries that Breakpad can use. This can also fail.
|
||||
// First, add a new stack frame entry, into which ExtabEntryDecode
|
||||
// will write the CFI entries.
|
||||
handler_->AddStackFrame(addr, next_addr - addr);
|
||||
int ret = ExtabEntryDecode(buf, buf_used);
|
||||
if (ret < 0) {
|
||||
handler_->DeleteStackFrame();
|
||||
BPLOG(INFO) << "ExtabEntryDecode: failed with error code: " << ret;
|
||||
continue;
|
||||
if (!handler_->HasStackFrame(addr, next_addr - addr)) {
|
||||
handler_->AddStackFrame(addr, next_addr - addr);
|
||||
int ret = ExtabEntryDecode(buf, buf_used);
|
||||
if (ret < 0) {
|
||||
handler_->DeleteStackFrame();
|
||||
BPLOG(INFO) << "ExtabEntryDecode: failed with error code: " << ret;
|
||||
continue;
|
||||
}
|
||||
handler_->SubmitStackFrame();
|
||||
}
|
||||
handler_->SubmitStackFrame();
|
||||
|
||||
} /* iterating over .exidx */
|
||||
}
|
||||
|
|
|
@ -162,6 +162,21 @@ int ARMExToModule::TranslateCmd(const struct extab_data* edata,
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool ARMExToModule::HasStackFrame(uintptr_t addr, size_t size) {
|
||||
// Invariant: the range [addr,covered) is covered by existing stack
|
||||
// frame entries.
|
||||
uintptr_t covered = addr;
|
||||
while (covered < addr + size) {
|
||||
const Module::StackFrameEntry *old_entry =
|
||||
module_->FindStackFrameEntryByAddress(covered);
|
||||
if (!old_entry) {
|
||||
return false;
|
||||
}
|
||||
covered = old_entry->address + old_entry->size;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ARMExToModule::AddStackFrame(uintptr_t addr, size_t size) {
|
||||
stack_frame_entry_ = new Module::StackFrameEntry;
|
||||
stack_frame_entry_->address = addr;
|
||||
|
|
|
@ -108,6 +108,7 @@ class ARMExToModule {
|
|||
ARMExToModule(Module* module)
|
||||
: module_(module) { }
|
||||
~ARMExToModule() { }
|
||||
bool HasStackFrame(uintptr_t addr, size_t size);
|
||||
void AddStackFrame(uintptr_t addr, size_t size);
|
||||
int ImproveStackFrame(const struct extab_data* edata);
|
||||
void DeleteStackFrame();
|
||||
|
|
|
@ -695,10 +695,12 @@ bool LoadSymbols(const string& obj_file,
|
|||
FindElfSectionByName<ElfClass>(".ARM.extab", SHT_PROGBITS,
|
||||
sections, names, names_end,
|
||||
elf_header->e_shnum);
|
||||
// Only load information from this section if there isn't a .debug_info
|
||||
// section.
|
||||
if (!found_debug_info_section
|
||||
&& arm_exidx_section && arm_extab_section && symbol_data != NO_CFI) {
|
||||
// Load information from these sections even if there is
|
||||
// .debug_info, because some functions (e.g., hand-written or
|
||||
// script-generated assembly) could have exidx entries but no DWARF.
|
||||
// (For functions with both, the DWARF info that has already been
|
||||
// parsed will take precedence.)
|
||||
if (arm_exidx_section && arm_extab_section && symbol_data != NO_CFI) {
|
||||
info->LoadedSection(".ARM.exidx");
|
||||
info->LoadedSection(".ARM.extab");
|
||||
bool result = LoadARMexidx<ElfClass>(elf_header,
|
||||
|
|
|
@ -89,6 +89,7 @@ BuiltinProvider.prototype = {
|
|||
"gcli": "resource://gre/modules/devtools/gcli",
|
||||
"acorn": "resource://gre/modules/devtools/acorn",
|
||||
"acorn/util/walk": "resource://gre/modules/devtools/acorn/walk.js",
|
||||
"tern": "resource://gre/modules/devtools/tern",
|
||||
|
||||
// Allow access to xpcshell test items from the loader.
|
||||
"xpcshell-test": "resource://test"
|
||||
|
@ -139,6 +140,7 @@ SrcdirProvider.prototype = {
|
|||
let gcliURI = this.fileURI(OS.Path.join(toolkitDir, "gcli", "source", "lib", "gcli"));
|
||||
let acornURI = this.fileURI(OS.Path.join(toolkitDir, "acorn"));
|
||||
let acornWalkURI = OS.Path.join(acornURI, "walk.js");
|
||||
let ternURI = OS.Path.join(toolkitDir, "tern");
|
||||
this.loader = new loader.Loader({
|
||||
modules: {
|
||||
"Debugger": Debugger,
|
||||
|
@ -165,7 +167,8 @@ SrcdirProvider.prototype = {
|
|||
"devtools/content-observer": contentObserverURI,
|
||||
"gcli": gcliURI,
|
||||
"acorn": acornURI,
|
||||
"acorn/util/walk": acornWalkURI
|
||||
"acorn/util/walk": acornWalkURI,
|
||||
"tern": ternURI
|
||||
},
|
||||
globals: loaderGlobals,
|
||||
invisibleToDebugger: this.invisibleToDebugger
|
||||
|
|
|
@ -2,6 +2,7 @@ const {Ci, Cc, Cu, Cr} = require("chrome");
|
|||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
|
||||
const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
|
||||
|
@ -83,6 +84,14 @@ function zipDirectory(zipFile, dirToArchive) {
|
|||
}
|
||||
|
||||
function uploadPackage(client, webappsActor, packageFile) {
|
||||
if (client.traits.bulk) {
|
||||
return uploadPackageBulk(client, webappsActor, packageFile);
|
||||
} else {
|
||||
return uploadPackageJSON(client, webappsActor, packageFile);
|
||||
}
|
||||
}
|
||||
|
||||
function uploadPackageJSON(client, webappsActor, packageFile) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let request = {
|
||||
|
@ -134,6 +143,43 @@ function uploadPackage(client, webappsActor, packageFile) {
|
|||
return deferred.promise;
|
||||
}
|
||||
|
||||
function uploadPackageBulk(client, webappsActor, packageFile) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let request = {
|
||||
to: webappsActor,
|
||||
type: "uploadPackage",
|
||||
bulk: true
|
||||
};
|
||||
client.request(request, (res) => {
|
||||
startBulkUpload(res.actor);
|
||||
});
|
||||
|
||||
function startBulkUpload(actor) {
|
||||
console.log("Starting bulk upload");
|
||||
let fileSize = packageFile.fileSize;
|
||||
console.log("File size: " + fileSize);
|
||||
|
||||
let request = client.startBulkRequest({
|
||||
actor: actor,
|
||||
type: "stream",
|
||||
length: fileSize
|
||||
});
|
||||
|
||||
request.on("bulk-send-ready", ({copyFrom}) => {
|
||||
NetUtil.asyncFetch(packageFile, function(inputStream) {
|
||||
copyFrom(inputStream).then(() => {
|
||||
console.log("Bulk upload done");
|
||||
inputStream.close();
|
||||
deferred.resolve(actor);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function removeServerTemporaryFile(client, fileActor) {
|
||||
let request = {
|
||||
to: fileActor,
|
||||
|
|
|
@ -174,12 +174,32 @@ add_test(function testUninstall() {
|
|||
|
||||
add_test(function testFileUploadInstall() {
|
||||
let packageFile = do_get_file("data/app.zip");
|
||||
|
||||
// Disable the bulk trait temporarily to test the JSON upload path
|
||||
gClient.traits.bulk = false;
|
||||
|
||||
installPackaged(gClient, gActor, packageFile.path, gAppId)
|
||||
.then(function ({ appId }) {
|
||||
do_check_eq(appId, gAppId);
|
||||
|
||||
// Restore default bulk trait value
|
||||
gClient.traits.bulk = true;
|
||||
|
||||
run_next_test();
|
||||
}, function (e) {
|
||||
do_throw("Failed install uploaded packaged app: " + e.error + ": " + e.message);
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function testBulkUploadInstall() {
|
||||
let packageFile = do_get_file("data/app.zip");
|
||||
do_check_true(gClient.traits.bulk);
|
||||
installPackaged(gClient, gActor, packageFile.path, gAppId)
|
||||
.then(function ({ appId }) {
|
||||
do_check_eq(appId, gAppId);
|
||||
run_next_test();
|
||||
}, function (e) {
|
||||
do_throw("Failed install uploaded packaged app: " + e.error + ": " + e.message);
|
||||
do_throw("Failed bulk install uploaded packaged app: " + e.error + ": " + e.message);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@ PARALLEL_DIRS += [
|
|||
'acorn',
|
||||
'pretty-fast',
|
||||
'qrcode',
|
||||
'transport'
|
||||
'transport',
|
||||
'tern',
|
||||
]
|
||||
|
||||
MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
|
||||
|
|
|
@ -12,6 +12,7 @@ let CC = Components.Constructor;
|
|||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
|
||||
|
@ -23,87 +24,157 @@ function debug(aMsg) {
|
|||
*/
|
||||
}
|
||||
|
||||
function PackageUploadActor(aPath, aFile) {
|
||||
this._path = aPath;
|
||||
this._file = aFile;
|
||||
this.size = 0;
|
||||
function PackageUploadActor(file) {
|
||||
this._file = file;
|
||||
this._path = file.path;
|
||||
}
|
||||
|
||||
PackageUploadActor.fromRequest = function(request, file) {
|
||||
if (request.bulk) {
|
||||
return new PackageUploadBulkActor(file);
|
||||
}
|
||||
return new PackageUploadJSONActor(file);
|
||||
};
|
||||
|
||||
PackageUploadActor.prototype = {
|
||||
actorPrefix: "packageUploadActor",
|
||||
|
||||
/**
|
||||
* This method isn't exposed to the client.
|
||||
* It is meant to be called by server code, in order to get
|
||||
* access to the temporary file out of the actor ID.
|
||||
*/
|
||||
getFilePath: function () {
|
||||
get filePath() {
|
||||
return this._path;
|
||||
},
|
||||
|
||||
/**
|
||||
* This method allows you to upload a piece of file.
|
||||
* It expects a chunk argument that is the a string to write to the file.
|
||||
*/
|
||||
chunk: function (aRequest) {
|
||||
let chunk = aRequest.chunk;
|
||||
if (!chunk || chunk.length <= 0) {
|
||||
return {error: "parameterError",
|
||||
message: "Missing or invalid chunk argument"};
|
||||
get openedFile() {
|
||||
if (this._openedFile) {
|
||||
return this._openedFile;
|
||||
}
|
||||
// Translate the string used to transfer the chunk over JSON
|
||||
// back to a typed array
|
||||
let data = new Uint8Array(chunk.length);
|
||||
for (let i = 0, l = chunk.length; i < l ; i++) {
|
||||
data[i] = chunk.charCodeAt(i);
|
||||
}
|
||||
return this._file.write(data)
|
||||
.then((written) => {
|
||||
this.size += written;
|
||||
return {
|
||||
written: written,
|
||||
size: this.size
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* This method needs to be called, when you are done uploading
|
||||
* chunks, before trying to access/use the temporary file.
|
||||
* Otherwise, the file may be partially written
|
||||
* and also be locked.
|
||||
*/
|
||||
done: function (aRequest) {
|
||||
this._file.close();
|
||||
return {};
|
||||
this._openedFile = this._openFile();
|
||||
return this._openedFile;
|
||||
},
|
||||
|
||||
/**
|
||||
* This method allows you to delete the temporary file,
|
||||
* when you are done using it.
|
||||
*/
|
||||
remove: function (aRequest) {
|
||||
remove: function () {
|
||||
this._cleanupFile();
|
||||
return {};
|
||||
},
|
||||
|
||||
_cleanupFile: function () {
|
||||
try {
|
||||
this._file.close();
|
||||
this._closeFile();
|
||||
} catch(e) {}
|
||||
try {
|
||||
OS.File.remove(this._path);
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new JSON package upload actor.
|
||||
* @param file nsIFile temporary file to write to
|
||||
*/
|
||||
function PackageUploadJSONActor(file) {
|
||||
PackageUploadActor.call(this, file);
|
||||
this._size = 0;
|
||||
}
|
||||
|
||||
PackageUploadJSONActor.prototype = Object.create(PackageUploadActor.prototype);
|
||||
|
||||
PackageUploadJSONActor.prototype.actorPrefix = "packageUploadJSONActor";
|
||||
|
||||
PackageUploadJSONActor.prototype._openFile = function() {
|
||||
return OS.File.open(this._path, { write: true, truncate: true });
|
||||
};
|
||||
|
||||
PackageUploadJSONActor.prototype._closeFile = function() {
|
||||
this.openedFile.then(file => file.close());
|
||||
};
|
||||
|
||||
/**
|
||||
* This method allows you to upload a piece of file.
|
||||
* It expects a chunk argument that is the a string to write to the file.
|
||||
*/
|
||||
PackageUploadJSONActor.prototype.chunk = function(aRequest) {
|
||||
let chunk = aRequest.chunk;
|
||||
if (!chunk || chunk.length <= 0) {
|
||||
return {error: "parameterError",
|
||||
message: "Missing or invalid chunk argument"};
|
||||
}
|
||||
// Translate the string used to transfer the chunk over JSON
|
||||
// back to a typed array
|
||||
let data = new Uint8Array(chunk.length);
|
||||
for (let i = 0, l = chunk.length; i < l ; i++) {
|
||||
data[i] = chunk.charCodeAt(i);
|
||||
}
|
||||
return this.openedFile
|
||||
.then(file => file.write(data))
|
||||
.then((written) => {
|
||||
this._size += written;
|
||||
return {
|
||||
written: written,
|
||||
_size: this._size
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This method needs to be called, when you are done uploading
|
||||
* chunks, before trying to access/use the temporary file.
|
||||
* Otherwise, the file may be partially written
|
||||
* and also be locked.
|
||||
*/
|
||||
PackageUploadJSONActor.prototype.done = function() {
|
||||
this._closeFile();
|
||||
return {};
|
||||
};
|
||||
|
||||
/**
|
||||
* The request types this actor can handle.
|
||||
*/
|
||||
PackageUploadActor.prototype.requestTypes = {
|
||||
"chunk": PackageUploadActor.prototype.chunk,
|
||||
"done": PackageUploadActor.prototype.done,
|
||||
"remove": PackageUploadActor.prototype.remove
|
||||
PackageUploadJSONActor.prototype.requestTypes = {
|
||||
"chunk": PackageUploadJSONActor.prototype.chunk,
|
||||
"done": PackageUploadJSONActor.prototype.done,
|
||||
"remove": PackageUploadJSONActor.prototype.remove
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new bulk package upload actor.
|
||||
* @param file nsIFile temporary file to write to
|
||||
*/
|
||||
function PackageUploadBulkActor(file) {
|
||||
PackageUploadActor.call(this, file);
|
||||
}
|
||||
|
||||
PackageUploadBulkActor.prototype = Object.create(PackageUploadActor.prototype);
|
||||
|
||||
PackageUploadBulkActor.prototype.actorPrefix = "packageUploadBulkActor";
|
||||
|
||||
PackageUploadBulkActor.prototype._openFile = function() {
|
||||
return FileUtils.openSafeFileOutputStream(this._file);
|
||||
};
|
||||
|
||||
PackageUploadBulkActor.prototype._closeFile = function() {
|
||||
FileUtils.closeSafeFileOutputStream(this.openedFile);
|
||||
};
|
||||
|
||||
PackageUploadBulkActor.prototype.stream = function({copyTo}) {
|
||||
copyTo(this.openedFile).then(() => {
|
||||
this._closeFile();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* The request types this actor can handle.
|
||||
*/
|
||||
PackageUploadBulkActor.prototype.requestTypes = {
|
||||
"stream": PackageUploadBulkActor.prototype.stream,
|
||||
"remove": PackageUploadBulkActor.prototype.remove
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -230,28 +301,38 @@ WebappsActor.prototype = {
|
|||
return type;
|
||||
},
|
||||
|
||||
uploadPackage: function () {
|
||||
debug("uploadPackage\n");
|
||||
_createTmpPackage: function() {
|
||||
let tmpDir = FileUtils.getDir("TmpD", ["file-upload"], true, false);
|
||||
if (!tmpDir.exists() || !tmpDir.isDirectory()) {
|
||||
return {error: "fileAccessError",
|
||||
message: "Unable to create temporary folder"};
|
||||
return {
|
||||
error: "fileAccessError",
|
||||
message: "Unable to create temporary folder"
|
||||
};
|
||||
}
|
||||
let tmpFile = tmpDir;
|
||||
tmpFile.append("package.zip");
|
||||
tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8));
|
||||
if (!tmpFile.exists() || !tmpDir.isFile()) {
|
||||
return {error: "fileAccessError",
|
||||
message: "Unable to create temporary file"};
|
||||
return {
|
||||
error: "fileAccessError",
|
||||
message: "Unable to create temporary file"
|
||||
};
|
||||
}
|
||||
return tmpFile;
|
||||
},
|
||||
|
||||
uploadPackage: function (request) {
|
||||
debug("uploadPackage");
|
||||
|
||||
let tmpFile = this._createTmpPackage();
|
||||
if ("error" in tmpFile) {
|
||||
return tmpFile;
|
||||
}
|
||||
|
||||
return OS.File.open(tmpFile.path, { write: true, truncate: true })
|
||||
.then((file) => {
|
||||
let actor = new PackageUploadActor(tmpFile.path, file);
|
||||
this._actorPool.addActor(actor);
|
||||
this._uploads.push(actor);
|
||||
return { actor: actor.actorID };
|
||||
});
|
||||
let actor = PackageUploadActor.fromRequest(request, tmpFile);
|
||||
this._actorPool.addActor(actor);
|
||||
this._uploads.push(actor);
|
||||
return { actor: actor.actorID };
|
||||
},
|
||||
|
||||
installHostedApp: function wa_actorInstallHosted(aDir, aId, aReceipts,
|
||||
|
@ -486,7 +567,7 @@ WebappsActor.prototype = {
|
|||
message: "Unable to find upload actor '" + aRequest.upload
|
||||
+ "'" };
|
||||
}
|
||||
let appFile = FileUtils.File(actor.getFilePath());
|
||||
let appFile = FileUtils.File(actor.filePath);
|
||||
if (!appFile.exists()) {
|
||||
return { error: "badParameter",
|
||||
message: "The uploaded file doesn't exist on device" };
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,87 @@
|
|||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
return mod(exports);
|
||||
if (typeof define == "function" && define.amd) // AMD
|
||||
return define(["exports"], mod);
|
||||
mod(tern.comment || (tern.comment = {}));
|
||||
})(function(exports) {
|
||||
function isSpace(ch) {
|
||||
return (ch < 14 && ch > 8) || ch === 32 || ch === 160;
|
||||
}
|
||||
|
||||
function onOwnLine(text, pos) {
|
||||
for (; pos > 0; --pos) {
|
||||
var ch = text.charCodeAt(pos - 1);
|
||||
if (ch == 10) break;
|
||||
if (!isSpace(ch)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Gather comments directly before a function
|
||||
exports.commentsBefore = function(text, pos) {
|
||||
var found = null, emptyLines = 0, topIsLineComment;
|
||||
out: while (pos > 0) {
|
||||
var prev = text.charCodeAt(pos - 1);
|
||||
if (prev == 10) {
|
||||
for (var scan = --pos, sawNonWS = false; scan > 0; --scan) {
|
||||
prev = text.charCodeAt(scan - 1);
|
||||
if (prev == 47 && text.charCodeAt(scan - 2) == 47) {
|
||||
if (!onOwnLine(text, scan - 2)) break out;
|
||||
var content = text.slice(scan, pos);
|
||||
if (!emptyLines && topIsLineComment) found[0] = content + "\n" + found[0];
|
||||
else (found || (found = [])).unshift(content);
|
||||
topIsLineComment = true;
|
||||
emptyLines = 0;
|
||||
pos = scan - 2;
|
||||
break;
|
||||
} else if (prev == 10) {
|
||||
if (!sawNonWS && ++emptyLines > 1) break out;
|
||||
break;
|
||||
} else if (!sawNonWS && !isSpace(prev)) {
|
||||
sawNonWS = true;
|
||||
}
|
||||
}
|
||||
} else if (prev == 47 && text.charCodeAt(pos - 2) == 42) {
|
||||
for (var scan = pos - 2; scan > 1; --scan) {
|
||||
if (text.charCodeAt(scan - 1) == 42 && text.charCodeAt(scan - 2) == 47) {
|
||||
if (!onOwnLine(text, scan - 2)) break out;
|
||||
(found || (found = [])).unshift(text.slice(scan, pos - 2));
|
||||
topIsLineComment = false;
|
||||
emptyLines = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos = scan - 2;
|
||||
} else if (isSpace(prev)) {
|
||||
--pos;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
};
|
||||
|
||||
exports.commentAfter = function(text, pos) {
|
||||
while (pos < text.length) {
|
||||
var next = text.charCodeAt(pos);
|
||||
if (next == 47) {
|
||||
var after = text.charCodeAt(pos + 1), end;
|
||||
if (after == 47) // line comment
|
||||
end = text.indexOf("\n", pos + 2);
|
||||
else if (after == 42) // block comment
|
||||
end = text.indexOf("*/", pos + 2);
|
||||
else
|
||||
return;
|
||||
return text.slice(pos + 2, end < 0 ? text.length : end);
|
||||
} else if (isSpace(next)) {
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.ensureCommentsBefore = function(text, node) {
|
||||
if (node.hasOwnProperty("commentsBefore")) return node.commentsBefore;
|
||||
return node.commentsBefore = exports.commentsBefore(text, node.start);
|
||||
};
|
||||
});
|
|
@ -0,0 +1,282 @@
|
|||
// Condensing an inferred set of types to a JSON description document.
|
||||
|
||||
// This code can be used to, after a library has been analyzed,
|
||||
// extract the types defined in that library and dump them as a JSON
|
||||
// structure (as parsed by def.js).
|
||||
|
||||
// The idea being that big libraries can be analyzed once, dumped, and
|
||||
// then cheaply included in later analysis.
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
return mod(exports, require("./infer"));
|
||||
if (typeof define == "function" && define.amd) // AMD
|
||||
return define(["exports", "./infer"], mod);
|
||||
mod(self.tern || (self.tern = {}), tern); // Plain browser env
|
||||
})(function(exports, infer) {
|
||||
"use strict";
|
||||
|
||||
exports.condense = function(origins, name, options) {
|
||||
if (typeof origins == "string") origins = [origins];
|
||||
var state = new State(origins, name || origins[0], options || {});
|
||||
|
||||
runPass(state.passes.preCondenseReach, state);
|
||||
|
||||
state.cx.topScope.path = "<top>";
|
||||
state.cx.topScope.reached("", state);
|
||||
for (var path in state.roots)
|
||||
reach(state.roots[path], null, path, state);
|
||||
for (var i = 0; i < state.patchUp.length; ++i)
|
||||
patchUpSimpleInstance(state.patchUp[i], state);
|
||||
|
||||
runPass(state.passes.postCondenseReach, state);
|
||||
|
||||
for (var path in state.types)
|
||||
store(createPath(path.split("."), state), state.types[path], state);
|
||||
for (var path in state.altPaths)
|
||||
storeAlt(path, state.altPaths[path], state);
|
||||
var hasDef = false;
|
||||
for (var _def in state.output["!define"]) { hasDef = true; break; }
|
||||
if (!hasDef) delete state.output["!define"];
|
||||
|
||||
runPass(state.passes.postCondense, state);
|
||||
|
||||
return simplify(state.output, state.options.sortOutput);
|
||||
};
|
||||
|
||||
function State(origins, name, options) {
|
||||
this.origins = origins;
|
||||
this.cx = infer.cx();
|
||||
this.passes = options.passes || this.cx.parent && this.cx.parent.passes || {};
|
||||
this.maxOrigin = -Infinity;
|
||||
for (var i = 0; i < origins.length; ++i)
|
||||
this.maxOrigin = Math.max(this.maxOrigin, this.cx.origins.indexOf(origins[i]));
|
||||
this.output = {"!name": name, "!define": {}};
|
||||
this.options = options;
|
||||
this.types = Object.create(null);
|
||||
this.altPaths = Object.create(null);
|
||||
this.patchUp = [];
|
||||
this.roots = Object.create(null);
|
||||
}
|
||||
|
||||
State.prototype.isTarget = function(origin) {
|
||||
return this.origins.indexOf(origin) > -1;
|
||||
};
|
||||
|
||||
State.prototype.getSpan = function(node) {
|
||||
if (this.options.spans == false || !this.isTarget(node.origin)) return null;
|
||||
if (node.span) return node.span;
|
||||
var srv = this.cx.parent, file;
|
||||
if (!srv || !node.originNode || !(file = srv.findFile(node.origin))) return null;
|
||||
var start = node.originNode.start, end = node.originNode.end;
|
||||
var pStart = file.asLineChar(start), pEnd = file.asLineChar(end);
|
||||
return start + "[" + pStart.line + ":" + pStart.ch + "]-" +
|
||||
end + "[" + pEnd.line + ":" + pEnd.ch + "]";
|
||||
};
|
||||
|
||||
function pathLen(path) {
|
||||
var len = 1, pos = 0, dot;
|
||||
while ((dot = path.indexOf(".", pos)) != -1) {
|
||||
pos = dot + 1;
|
||||
len += path.charAt(pos) == "!" ? 10 : 1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
function isConcrete(path) {
|
||||
return !/\!|<i>/.test(path);
|
||||
}
|
||||
|
||||
function hop(obj, prop) {
|
||||
return Object.prototype.hasOwnProperty.call(obj, prop);
|
||||
}
|
||||
|
||||
function isSimpleInstance(o) {
|
||||
return o.proto && !(o instanceof infer.Fn) && o.proto != infer.cx().protos.Object &&
|
||||
o.proto.hasCtor && !o.hasCtor;
|
||||
}
|
||||
|
||||
function reach(type, path, id, state) {
|
||||
var actual = type.getType(false);
|
||||
if (!actual) return;
|
||||
var orig = type.origin || actual.origin, relevant = false;
|
||||
if (orig) {
|
||||
var origPos = state.cx.origins.indexOf(orig);
|
||||
// This is a path that is newer than the code we are interested in.
|
||||
if (origPos > state.maxOrigin) return;
|
||||
relevant = state.isTarget(orig);
|
||||
}
|
||||
var newPath = path ? path + "." + id : id, oldPath = actual.path;
|
||||
var shorter = !oldPath || pathLen(oldPath) > pathLen(newPath);
|
||||
if (shorter) {
|
||||
if (!(actual instanceof infer.Prim)) actual.path = newPath;
|
||||
if (actual.reached(newPath, state, !relevant) && relevant) {
|
||||
var data = state.types[oldPath];
|
||||
if (data) {
|
||||
delete state.types[oldPath];
|
||||
state.altPaths[oldPath] = actual;
|
||||
} else data = {type: actual};
|
||||
data.span = state.getSpan(type) || (actual != type && state.isTarget(actual.origin) && state.getSpan(actual)) || data.span;
|
||||
data.doc = type.doc || (actual != type && state.isTarget(actual.origin) && type.doc) || data.doc;
|
||||
data.data = actual.metaData;
|
||||
state.types[newPath] = data;
|
||||
}
|
||||
} else {
|
||||
if (relevant) state.altPaths[newPath] = actual;
|
||||
}
|
||||
}
|
||||
function reachTypeOnly(aval, path, id, state) {
|
||||
var type = aval.getType();
|
||||
if (type) reach(type, path, id, state);
|
||||
}
|
||||
|
||||
infer.Prim.prototype.reached = function() {return true;};
|
||||
|
||||
infer.Arr.prototype.reached = function(path, state, concrete) {
|
||||
if (!concrete) reachTypeOnly(this.getProp("<i>"), path, "<i>", state);
|
||||
return true;
|
||||
};
|
||||
|
||||
infer.Fn.prototype.reached = function(path, state, concrete) {
|
||||
infer.Obj.prototype.reached.call(this, path, state, concrete);
|
||||
if (!concrete) {
|
||||
for (var i = 0; i < this.args.length; ++i)
|
||||
reachTypeOnly(this.args[i], path, "!" + i, state);
|
||||
reachTypeOnly(this.retval, path, "!ret", state);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
infer.Obj.prototype.reached = function(path, state, concrete) {
|
||||
if (isSimpleInstance(this) && !this.condenseForceInclude) {
|
||||
if (state.patchUp.indexOf(this) == -1) state.patchUp.push(this);
|
||||
return true;
|
||||
} else if (this.proto && !concrete) {
|
||||
reach(this.proto, path, "!proto", state);
|
||||
}
|
||||
var hasProps = false;
|
||||
for (var prop in this.props) {
|
||||
reach(this.props[prop], path, prop, state);
|
||||
hasProps = true;
|
||||
}
|
||||
if (!hasProps && !this.condenseForceInclude && !(this instanceof infer.Fn)) {
|
||||
this.nameOverride = "?";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
function patchUpSimpleInstance(obj, state) {
|
||||
var path = obj.proto.hasCtor.path;
|
||||
if (path) {
|
||||
obj.nameOverride = "+" + path;
|
||||
} else {
|
||||
path = obj.path;
|
||||
}
|
||||
for (var prop in obj.props)
|
||||
reach(obj.props[prop], path, prop, state);
|
||||
}
|
||||
|
||||
function createPath(parts, state) {
|
||||
var base = state.output;
|
||||
for (var i = parts.length - 1; i >= 0; --i) if (!isConcrete(parts[i])) {
|
||||
var def = parts.slice(0, i + 1).join(".");
|
||||
var defs = state.output["!define"];
|
||||
if (hop(defs, def)) base = defs[def];
|
||||
else defs[def] = base = {};
|
||||
parts = parts.slice(i + 1);
|
||||
}
|
||||
for (var i = 0; i < parts.length; ++i) {
|
||||
if (hop(base, parts[i])) base = base[parts[i]];
|
||||
else base = base[parts[i]] = {};
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
function store(out, info, state) {
|
||||
var name = typeName(info.type);
|
||||
if (name != info.type.path && name != "?") {
|
||||
out["!type"] = name;
|
||||
} else if (info.type.proto && info.type.proto != state.cx.protos.Object) {
|
||||
var protoName = typeName(info.type.proto);
|
||||
if (protoName != "?") out["!proto"] = protoName;
|
||||
}
|
||||
if (info.span) out["!span"] = info.span;
|
||||
if (info.doc) out["!doc"] = info.doc;
|
||||
if (info.data) out["!data"] = info.data;
|
||||
}
|
||||
|
||||
function storeAlt(path, type, state) {
|
||||
var parts = path.split("."), last = parts.pop();
|
||||
var base = createPath(parts, state);
|
||||
if (!hop(base, last)) base[last] = type.nameOverride || type.path;
|
||||
}
|
||||
|
||||
var typeNameStack = [];
|
||||
function typeName(type) {
|
||||
var actual = type.getType(false);
|
||||
if (!actual || typeNameStack.indexOf(actual) > -1)
|
||||
return actual && actual.path || "?";
|
||||
typeNameStack.push(actual);
|
||||
var name = actual.typeName();
|
||||
typeNameStack.pop();
|
||||
return name;
|
||||
}
|
||||
|
||||
infer.Prim.prototype.typeName = function() { return this.name; };
|
||||
|
||||
infer.Arr.prototype.typeName = function() {
|
||||
return "[" + typeName(this.getProp("<i>")) + "]";
|
||||
};
|
||||
|
||||
infer.Fn.prototype.typeName = function() {
|
||||
var out = "fn(";
|
||||
for (var i = 0; i < this.args.length; ++i) {
|
||||
if (i) out += ", ";
|
||||
var name = this.argNames[i];
|
||||
if (name && name != "?") out += name + ": ";
|
||||
out += typeName(this.args[i]);
|
||||
}
|
||||
out += ")";
|
||||
if (this.computeRetSource) {
|
||||
out += " -> " + this.computeRetSource;
|
||||
} else if (!this.retval.isEmpty()) {
|
||||
var rettype = this.retval.getType(false);
|
||||
if (rettype) out += " -> " + typeName(rettype);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
infer.Obj.prototype.typeName = function() {
|
||||
if (this.nameOverride) return this.nameOverride;
|
||||
if (!this.path) return "?";
|
||||
return this.path;
|
||||
};
|
||||
|
||||
function simplify(data, sort) {
|
||||
if (typeof data != "object") return data;
|
||||
var sawType = false, sawOther = false;
|
||||
for (var prop in data) {
|
||||
if (prop == "!type") sawType = true;
|
||||
else sawOther = true;
|
||||
if (prop != "!data")
|
||||
data[prop] = simplify(data[prop], sort);
|
||||
}
|
||||
if (sawType && !sawOther) return data["!type"];
|
||||
return sort ? sortObject(data) : data;
|
||||
}
|
||||
|
||||
function sortObject(obj) {
|
||||
var props = [], out = {};
|
||||
for (var prop in obj) props.push({name: prop, val: obj[prop]});
|
||||
props.sort(function(a, b) { return a.name < b.name ? -1 : 1; });
|
||||
for (var i = 0; i < props.length; ++i)
|
||||
out[props[i].name] = props[i].val;
|
||||
return out;
|
||||
}
|
||||
|
||||
function runPass(functions) {
|
||||
if (functions) for (var i = 0; i < functions.length; ++i)
|
||||
functions[i].apply(null, Array.prototype.slice.call(arguments, 1));
|
||||
}
|
||||
});
|
|
@ -0,0 +1,462 @@
|
|||
// Type description parser
|
||||
|
||||
// Type description JSON files (such as ecma5.json and browser.json)
|
||||
// are used to
|
||||
//
|
||||
// A) describe types that come from native code
|
||||
|
||||
// B) to cheaply load the types for big libraries, or libraries that
|
||||
// can't be inferred well
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
return exports.init = mod;
|
||||
if (typeof define == "function" && define.amd) // AMD
|
||||
return define({init: mod});
|
||||
tern.def = {init: mod};
|
||||
})(function(exports, infer) {
|
||||
"use strict";
|
||||
|
||||
function hop(obj, prop) {
|
||||
return Object.prototype.hasOwnProperty.call(obj, prop);
|
||||
}
|
||||
|
||||
var TypeParser = exports.TypeParser = function(spec, start, base, forceNew) {
|
||||
this.pos = start || 0;
|
||||
this.spec = spec;
|
||||
this.base = base;
|
||||
this.forceNew = forceNew;
|
||||
};
|
||||
TypeParser.prototype = {
|
||||
eat: function(str) {
|
||||
if (str.length == 1 ? this.spec.charAt(this.pos) == str : this.spec.indexOf(str, this.pos) == this.pos) {
|
||||
this.pos += str.length;
|
||||
return true;
|
||||
}
|
||||
},
|
||||
word: function(re) {
|
||||
var word = "", ch, re = re || /[\w$]/;
|
||||
while ((ch = this.spec.charAt(this.pos)) && re.test(ch)) { word += ch; ++this.pos; }
|
||||
return word;
|
||||
},
|
||||
error: function() {
|
||||
throw new Error("Unrecognized type spec: " + this.spec + " (at " + this.pos + ")");
|
||||
},
|
||||
parseFnType: function(name, top) {
|
||||
var args = [], names = [];
|
||||
if (!this.eat(")")) for (var i = 0; ; ++i) {
|
||||
var colon = this.spec.indexOf(": ", this.pos), argname;
|
||||
if (colon != -1) {
|
||||
argname = this.spec.slice(this.pos, colon);
|
||||
if (/^[$\w?]+$/.test(argname))
|
||||
this.pos = colon + 2;
|
||||
else
|
||||
argname = null;
|
||||
}
|
||||
names.push(argname);
|
||||
args.push(this.parseType());
|
||||
if (!this.eat(", ")) {
|
||||
this.eat(")") || this.error();
|
||||
break;
|
||||
}
|
||||
}
|
||||
var retType, computeRet, computeRetStart, fn;
|
||||
if (this.eat(" -> ")) {
|
||||
if (top && this.spec.indexOf("!", this.pos) > -1) {
|
||||
retType = infer.ANull;
|
||||
computeRetStart = this.pos;
|
||||
computeRet = this.parseRetType();
|
||||
} else retType = this.parseType();
|
||||
} else retType = infer.ANull;
|
||||
if (top && (fn = this.base))
|
||||
infer.Fn.call(this.base, name, infer.ANull, args, names, retType);
|
||||
else
|
||||
fn = new infer.Fn(name, infer.ANull, args, names, retType);
|
||||
if (computeRet) fn.computeRet = computeRet;
|
||||
if (computeRetStart != null) fn.computeRetSource = this.spec.slice(computeRetStart, this.pos);
|
||||
return fn;
|
||||
},
|
||||
parseType: function(name, top) {
|
||||
if (this.eat("fn(")) {
|
||||
return this.parseFnType(name, top);
|
||||
} else if (this.eat("[")) {
|
||||
var inner = this.parseType();
|
||||
this.eat("]") || this.error();
|
||||
if (top && this.base) {
|
||||
infer.Arr.call(this.base, inner);
|
||||
return this.base;
|
||||
}
|
||||
return new infer.Arr(inner);
|
||||
} else if (this.eat("+")) {
|
||||
var path = this.word(/[\w$<>\.!]/);
|
||||
var base = parsePath(path + ".prototype");
|
||||
if (!(base instanceof infer.Obj)) base = parsePath(path);
|
||||
if (!(base instanceof infer.Obj)) return base;
|
||||
if (top && this.forceNew) return new infer.Obj(base);
|
||||
return infer.getInstance(base);
|
||||
} else if (this.eat("?")) {
|
||||
return infer.ANull;
|
||||
} else {
|
||||
return this.fromWord(this.word(/[\w$<>\.!`]/));
|
||||
}
|
||||
},
|
||||
fromWord: function(spec) {
|
||||
var cx = infer.cx();
|
||||
switch (spec) {
|
||||
case "number": return cx.num;
|
||||
case "string": return cx.str;
|
||||
case "bool": return cx.bool;
|
||||
case "<top>": return cx.topScope;
|
||||
}
|
||||
if (cx.localDefs && spec in cx.localDefs) return cx.localDefs[spec];
|
||||
return parsePath(spec);
|
||||
},
|
||||
parseBaseRetType: function() {
|
||||
if (this.eat("[")) {
|
||||
var inner = this.parseRetType();
|
||||
this.eat("]") || this.error();
|
||||
return function(self, args) { return new infer.Arr(inner(self, args)); };
|
||||
} else if (this.eat("+")) {
|
||||
var base = this.parseRetType();
|
||||
return function(self, args) { return infer.getInstance(base(self, args)); };
|
||||
} else if (this.eat("!")) {
|
||||
var arg = this.word(/\d/);
|
||||
if (arg) {
|
||||
arg = Number(arg);
|
||||
return function(_self, args) {return args[arg] || infer.ANull;};
|
||||
} else if (this.eat("this")) {
|
||||
return function(self) {return self;};
|
||||
} else if (this.eat("custom:")) {
|
||||
var fname = this.word(/[\w$]/);
|
||||
return customFunctions[fname] || function() { return infer.ANull; };
|
||||
} else {
|
||||
return this.fromWord("!" + arg + this.word(/[\w$<>\.!]/));
|
||||
}
|
||||
}
|
||||
var t = this.parseType();
|
||||
return function(){return t;};
|
||||
},
|
||||
extendRetType: function(base) {
|
||||
var propName = this.word(/[\w<>$!]/) || this.error();
|
||||
if (propName == "!ret") return function(self, args) {
|
||||
var lhs = base(self, args);
|
||||
if (lhs.retval) return lhs.retval;
|
||||
var rv = new infer.AVal;
|
||||
lhs.propagate(new infer.IsCallee(infer.ANull, [], null, rv));
|
||||
return rv;
|
||||
};
|
||||
return function(self, args) {return base(self, args).getProp(propName);};
|
||||
},
|
||||
parseRetType: function() {
|
||||
var tp = this.parseBaseRetType();
|
||||
while (this.eat(".")) tp = this.extendRetType(tp);
|
||||
return tp;
|
||||
}
|
||||
};
|
||||
|
||||
function parseType(spec, name, base, forceNew) {
|
||||
var type = new TypeParser(spec, null, base, forceNew).parseType(name, true);
|
||||
if (/^fn\(/.test(spec)) for (var i = 0; i < type.args.length; ++i) (function(i) {
|
||||
var arg = type.args[i];
|
||||
if (arg instanceof infer.Fn && arg.args && arg.args.length) addEffect(type, function(_self, fArgs) {
|
||||
var fArg = fArgs[i];
|
||||
if (fArg) fArg.propagate(new infer.IsCallee(infer.cx().topScope, arg.args, null, infer.ANull));
|
||||
});
|
||||
})(i);
|
||||
return type;
|
||||
}
|
||||
|
||||
function addEffect(fn, handler, replaceRet) {
|
||||
var oldCmp = fn.computeRet, rv = fn.retval;
|
||||
fn.computeRet = function(self, args, argNodes) {
|
||||
var handled = handler(self, args, argNodes);
|
||||
var old = oldCmp ? oldCmp(self, args, argNodes) : rv;
|
||||
return replaceRet ? handled : old;
|
||||
};
|
||||
}
|
||||
|
||||
var parseEffect = exports.parseEffect = function(effect, fn) {
|
||||
var m;
|
||||
if (effect.indexOf("propagate ") == 0) {
|
||||
var p = new TypeParser(effect, 10);
|
||||
var getOrigin = p.parseRetType();
|
||||
if (!p.eat(" ")) p.error();
|
||||
var getTarget = p.parseRetType();
|
||||
addEffect(fn, function(self, args) {
|
||||
getOrigin(self, args).propagate(getTarget(self, args));
|
||||
});
|
||||
} else if (effect.indexOf("call ") == 0) {
|
||||
var andRet = effect.indexOf("and return ", 5) == 5;
|
||||
var p = new TypeParser(effect, andRet ? 16 : 5);
|
||||
var getCallee = p.parseRetType(), getSelf = null, getArgs = [];
|
||||
if (p.eat(" this=")) getSelf = p.parseRetType();
|
||||
while (p.eat(" ")) getArgs.push(p.parseRetType());
|
||||
addEffect(fn, function(self, args) {
|
||||
var callee = getCallee(self, args);
|
||||
var slf = getSelf ? getSelf(self, args) : infer.ANull, as = [];
|
||||
for (var i = 0; i < getArgs.length; ++i) as.push(getArgs[i](self, args));
|
||||
var result = andRet ? new infer.AVal : infer.ANull;
|
||||
callee.propagate(new infer.IsCallee(slf, as, null, result));
|
||||
return result;
|
||||
}, andRet);
|
||||
} else if (m = effect.match(/^custom (\S+)\s*(.*)/)) {
|
||||
var customFunc = customFunctions[m[1]];
|
||||
if (customFunc) addEffect(fn, m[2] ? customFunc(m[2]) : customFunc);
|
||||
} else if (effect.indexOf("copy ") == 0) {
|
||||
var p = new TypeParser(effect, 5);
|
||||
var getFrom = p.parseRetType();
|
||||
p.eat(" ");
|
||||
var getTo = p.parseRetType();
|
||||
addEffect(fn, function(self, args) {
|
||||
var from = getFrom(self, args), to = getTo(self, args);
|
||||
from.forAllProps(function(prop, val, local) {
|
||||
if (local && prop != "<i>")
|
||||
to.propagate(new infer.PropHasSubset(prop, val));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
throw new Error("Unknown effect type: " + effect);
|
||||
}
|
||||
};
|
||||
|
||||
var currentTopScope;
|
||||
|
||||
var parsePath = exports.parsePath = function(path) {
|
||||
var cx = infer.cx(), cached = cx.paths[path], origPath = path;
|
||||
if (cached != null) return cached;
|
||||
cx.paths[path] = infer.ANull;
|
||||
|
||||
var base = currentTopScope || cx.topScope;
|
||||
|
||||
if (cx.localDefs) for (var name in cx.localDefs) {
|
||||
if (path.indexOf(name) == 0) {
|
||||
if (path == name) return cx.paths[path] = cx.localDefs[path];
|
||||
if (path.charAt(name.length) == ".") {
|
||||
base = cx.localDefs[name];
|
||||
path = path.slice(name.length + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var parts = path.split(".");
|
||||
for (var i = 0; i < parts.length && base != infer.ANull; ++i) {
|
||||
var prop = parts[i];
|
||||
if (prop.charAt(0) == "!") {
|
||||
if (prop == "!proto") {
|
||||
base = (base instanceof infer.Obj && base.proto) || infer.ANull;
|
||||
} else {
|
||||
var fn = base.getFunctionType();
|
||||
if (!fn) {
|
||||
base = infer.ANull;
|
||||
} else if (prop == "!ret") {
|
||||
base = fn.retval && fn.retval.getType() || infer.ANull;
|
||||
} else {
|
||||
var arg = fn.args && fn.args[Number(prop.slice(1))];
|
||||
base = (arg && arg.getType()) || infer.ANull;
|
||||
}
|
||||
}
|
||||
} else if (base instanceof infer.Obj) {
|
||||
var propVal = (prop == "prototype" && base instanceof infer.Fn) ? base.getProp(prop) : base.props[prop];
|
||||
if (!propVal || propVal.isEmpty())
|
||||
base = infer.ANull;
|
||||
else
|
||||
base = propVal.types[0];
|
||||
}
|
||||
}
|
||||
// Uncomment this to get feedback on your poorly written .json files
|
||||
// if (base == infer.ANull) console.error("bad path: " + origPath + " (" + cx.curOrigin + ")");
|
||||
cx.paths[origPath] = base == infer.ANull ? null : base;
|
||||
return base;
|
||||
};
|
||||
|
||||
function emptyObj(ctor) {
|
||||
var empty = Object.create(ctor.prototype);
|
||||
empty.props = Object.create(null);
|
||||
empty.isShell = true;
|
||||
return empty;
|
||||
}
|
||||
|
||||
function isSimpleAnnotation(spec) {
|
||||
if (!spec["!type"] || /^fn\(/.test(spec["!type"])) return false;
|
||||
for (var prop in spec)
|
||||
if (prop != "!type" && prop != "!doc" && prop != "!url" && prop != "!span" && prop != "!data")
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function passOne(base, spec, path) {
|
||||
if (!base) {
|
||||
var tp = spec["!type"];
|
||||
if (tp) {
|
||||
if (/^fn\(/.test(tp)) base = emptyObj(infer.Fn);
|
||||
else if (tp.charAt(0) == "[") base = emptyObj(infer.Arr);
|
||||
else throw new Error("Invalid !type spec: " + tp);
|
||||
} else if (spec["!stdProto"]) {
|
||||
base = infer.cx().protos[spec["!stdProto"]];
|
||||
} else {
|
||||
base = emptyObj(infer.Obj);
|
||||
}
|
||||
base.name = path;
|
||||
}
|
||||
|
||||
for (var name in spec) if (hop(spec, name) && name.charCodeAt(0) != 33) {
|
||||
var inner = spec[name];
|
||||
if (typeof inner == "string" || isSimpleAnnotation(inner)) continue;
|
||||
var prop = base.defProp(name);
|
||||
passOne(prop.getType(), inner, path ? path + "." + name : name).propagate(prop);
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
function passTwo(base, spec, path) {
|
||||
if (base.isShell) {
|
||||
delete base.isShell;
|
||||
var tp = spec["!type"];
|
||||
if (tp) {
|
||||
parseType(tp, path, base);
|
||||
} else {
|
||||
var proto = spec["!proto"] && parseType(spec["!proto"]);
|
||||
infer.Obj.call(base, proto instanceof infer.Obj ? proto : true, path);
|
||||
}
|
||||
}
|
||||
|
||||
var effects = spec["!effects"];
|
||||
if (effects && base instanceof infer.Fn) for (var i = 0; i < effects.length; ++i)
|
||||
parseEffect(effects[i], base);
|
||||
copyInfo(spec, base);
|
||||
|
||||
for (var name in spec) if (hop(spec, name) && name.charCodeAt(0) != 33) {
|
||||
var inner = spec[name], known = base.defProp(name), innerPath = path ? path + "." + name : name;
|
||||
var type = known.getType();
|
||||
if (typeof inner == "string") {
|
||||
if (type) continue;
|
||||
parseType(inner, innerPath).propagate(known);
|
||||
} else {
|
||||
if (!isSimpleAnnotation(inner)) {
|
||||
passTwo(type, inner, innerPath);
|
||||
} else if (!type) {
|
||||
parseType(inner["!type"], innerPath, null, true).propagate(known);
|
||||
type = known.getType();
|
||||
if (type instanceof infer.Obj) copyInfo(inner, type);
|
||||
} else continue;
|
||||
if (inner["!doc"]) known.doc = inner["!doc"];
|
||||
if (inner["!url"]) known.url = inner["!url"];
|
||||
if (inner["!span"]) known.span = inner["!span"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copyInfo(spec, type) {
|
||||
if (spec["!doc"]) type.doc = spec["!doc"];
|
||||
if (spec["!url"]) type.url = spec["!url"];
|
||||
if (spec["!span"]) type.span = spec["!span"];
|
||||
if (spec["!data"]) type.metaData = spec["!data"];
|
||||
}
|
||||
|
||||
function runPasses(type, arg) {
|
||||
var parent = infer.cx().parent, pass = parent && parent.passes && parent.passes[type];
|
||||
if (pass) for (var i = 0; i < pass.length; i++) pass[i](arg);
|
||||
}
|
||||
|
||||
function doLoadEnvironment(data, scope) {
|
||||
var cx = infer.cx();
|
||||
|
||||
infer.addOrigin(cx.curOrigin = data["!name"] || "env#" + cx.origins.length);
|
||||
cx.localDefs = cx.definitions[cx.curOrigin] = Object.create(null);
|
||||
|
||||
runPasses("preLoadDef", data);
|
||||
|
||||
passOne(scope, data);
|
||||
|
||||
var def = data["!define"];
|
||||
if (def) {
|
||||
for (var name in def) {
|
||||
var spec = def[name];
|
||||
cx.localDefs[name] = typeof spec == "string" ? parsePath(spec) : passOne(null, spec, name);
|
||||
}
|
||||
for (var name in def) {
|
||||
var spec = def[name];
|
||||
if (typeof spec != "string") passTwo(cx.localDefs[name], def[name], name);
|
||||
}
|
||||
}
|
||||
|
||||
passTwo(scope, data);
|
||||
|
||||
runPasses("postLoadDef", data);
|
||||
|
||||
cx.curOrigin = cx.localDefs = null;
|
||||
}
|
||||
|
||||
exports.load = function(data, scope) {
|
||||
if (!scope) scope = infer.cx().topScope;
|
||||
var oldScope = currentTopScope;
|
||||
currentTopScope = scope;
|
||||
try {
|
||||
doLoadEnvironment(data, scope);
|
||||
} finally {
|
||||
currentTopScope = oldScope;
|
||||
}
|
||||
};
|
||||
|
||||
// Used to register custom logic for more involved effect or type
|
||||
// computation.
|
||||
var customFunctions = Object.create(null);
|
||||
infer.registerFunction = function(name, f) { customFunctions[name] = f; };
|
||||
|
||||
var IsCreated = infer.constraint("created, target, spec", {
|
||||
addType: function(tp) {
|
||||
if (tp instanceof infer.Obj && this.created++ < 5) {
|
||||
var derived = new infer.Obj(tp), spec = this.spec;
|
||||
if (spec instanceof infer.AVal) spec = spec.getType();
|
||||
if (spec instanceof infer.Obj) for (var prop in spec.props) {
|
||||
var cur = spec.props[prop].types[0];
|
||||
var p = derived.defProp(prop);
|
||||
if (cur && cur instanceof infer.Obj && cur.props.value) {
|
||||
var vtp = cur.props.value.getType();
|
||||
if (vtp) p.addType(vtp);
|
||||
}
|
||||
}
|
||||
this.target.addType(derived);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
infer.registerFunction("Object_create", function(_self, args, argNodes) {
|
||||
if (argNodes && argNodes.length && argNodes[0].type == "Literal" && argNodes[0].value == null)
|
||||
return new infer.Obj();
|
||||
|
||||
var result = new infer.AVal;
|
||||
if (args[0]) args[0].propagate(new IsCreated(0, result, args[1]));
|
||||
return result;
|
||||
});
|
||||
|
||||
var IsBound = infer.constraint("self, args, target", {
|
||||
addType: function(tp) {
|
||||
if (!(tp instanceof infer.Fn)) return;
|
||||
this.target.addType(new infer.Fn(tp.name, tp.self, tp.args.slice(this.args.length),
|
||||
tp.argNames.slice(this.args.length), tp.retval));
|
||||
this.self.propagate(tp.self);
|
||||
for (var i = 0; i < Math.min(tp.args.length, this.args.length); ++i)
|
||||
this.args[i].propagate(tp.args[i]);
|
||||
}
|
||||
});
|
||||
|
||||
infer.registerFunction("Function_bind", function(self, args) {
|
||||
if (!args.length) return infer.ANull;
|
||||
var result = new infer.AVal;
|
||||
self.propagate(new IsBound(args[0], args.slice(1), result));
|
||||
return result;
|
||||
});
|
||||
|
||||
infer.registerFunction("Array_ctor", function(_self, args) {
|
||||
var arr = new infer.Arr;
|
||||
if (args.length != 1 || !args[0].hasType(infer.cx().num)) {
|
||||
var content = arr.getProp("<i>");
|
||||
for (var i = 0; i < args.length; ++i) args[i].propagate(content);
|
||||
}
|
||||
return arr;
|
||||
});
|
||||
|
||||
return exports;
|
||||
});
|
|
@ -0,0 +1,950 @@
|
|||
module.exports = {
|
||||
"!name": "ecma5",
|
||||
"!define": {"Error.prototype": "Error.prototype"},
|
||||
"Infinity": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Infinity",
|
||||
"!doc": "A numeric value representing infinity."
|
||||
},
|
||||
"undefined": {
|
||||
"!type": "?",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/undefined",
|
||||
"!doc": "The value undefined."
|
||||
},
|
||||
"NaN": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/NaN",
|
||||
"!doc": "A value representing Not-A-Number."
|
||||
},
|
||||
"Object": {
|
||||
"!type": "fn()",
|
||||
"getPrototypeOf": {
|
||||
"!type": "fn(obj: ?) -> ?",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/getPrototypeOf",
|
||||
"!doc": "Returns the prototype (i.e. the internal prototype) of the specified object."
|
||||
},
|
||||
"create": {
|
||||
"!type": "fn(proto: ?) -> !custom:Object_create",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create",
|
||||
"!doc": "Creates a new object with the specified prototype object and properties."
|
||||
},
|
||||
"defineProperty": {
|
||||
"!type": "fn(obj: ?, prop: string, desc: ?)",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty",
|
||||
"!doc": "Defines a new property directly on an object, or modifies an existing property on an object, and returns the object. If you want to see how to use the Object.defineProperty method with a binary-flags-like syntax, see this article."
|
||||
},
|
||||
"defineProperties": {
|
||||
"!type": "fn(obj: ?, props: ?)",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty",
|
||||
"!doc": "Defines a new property directly on an object, or modifies an existing property on an object, and returns the object. If you want to see how to use the Object.defineProperty method with a binary-flags-like syntax, see this article."
|
||||
},
|
||||
"getOwnPropertyDescriptor": {
|
||||
"!type": "fn(obj: ?, prop: string) -> ?",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor",
|
||||
"!doc": "Returns a property descriptor for an own property (that is, one directly present on an object, not present by dint of being along an object's prototype chain) of a given object."
|
||||
},
|
||||
"keys": {
|
||||
"!type": "fn(obj: ?) -> [string]",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys",
|
||||
"!doc": "Returns an array of a given object's own enumerable properties, in the same order as that provided by a for-in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well)."
|
||||
},
|
||||
"getOwnPropertyNames": {
|
||||
"!type": "fn(obj: ?) -> [string]",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames",
|
||||
"!doc": "Returns an array of all properties (enumerable or not) found directly upon a given object."
|
||||
},
|
||||
"seal": {
|
||||
"!type": "fn(obj: ?)",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/seal",
|
||||
"!doc": "Seals an object, preventing new properties from being added to it and marking all existing properties as non-configurable. Values of present properties can still be changed as long as they are writable."
|
||||
},
|
||||
"isSealed": {
|
||||
"!type": "fn(obj: ?) -> bool",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/isSealed",
|
||||
"!doc": "Determine if an object is sealed."
|
||||
},
|
||||
"freeze": {
|
||||
"!type": "fn(obj: ?)",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/freeze",
|
||||
"!doc": "Freezes an object: that is, prevents new properties from being added to it; prevents existing properties from being removed; and prevents existing properties, or their enumerability, configurability, or writability, from being changed. In essence the object is made effectively immutable. The method returns the object being frozen."
|
||||
},
|
||||
"isFrozen": {
|
||||
"!type": "fn(obj: ?) -> bool",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/isFrozen",
|
||||
"!doc": "Determine if an object is frozen."
|
||||
},
|
||||
"prototype": {
|
||||
"!stdProto": "Object",
|
||||
"toString": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/toString",
|
||||
"!doc": "Returns a string representing the object."
|
||||
},
|
||||
"toLocaleString": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/toLocaleString",
|
||||
"!doc": "Returns a string representing the object. This method is meant to be overriden by derived objects for locale-specific purposes."
|
||||
},
|
||||
"valueOf": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/valueOf",
|
||||
"!doc": "Returns the primitive value of the specified object"
|
||||
},
|
||||
"hasOwnProperty": {
|
||||
"!type": "fn(prop: string) -> bool",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/hasOwnProperty",
|
||||
"!doc": "Returns a boolean indicating whether the object has the specified property."
|
||||
},
|
||||
"propertyIsEnumerable": {
|
||||
"!type": "fn(prop: string) -> bool",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/propertyIsEnumerable",
|
||||
"!doc": "Returns a Boolean indicating whether the specified property is enumerable."
|
||||
}
|
||||
},
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object",
|
||||
"!doc": "Creates an object wrapper."
|
||||
},
|
||||
"Function": {
|
||||
"!type": "fn(body: string) -> fn()",
|
||||
"prototype": {
|
||||
"!stdProto": "Function",
|
||||
"apply": {
|
||||
"!type": "fn(this: ?, args: [?])",
|
||||
"!effects": [
|
||||
"call and return !this this=!0 !1.<i> !1.<i> !1.<i>"
|
||||
],
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply",
|
||||
"!doc": "Calls a function with a given this value and arguments provided as an array (or an array like object)."
|
||||
},
|
||||
"call": {
|
||||
"!type": "fn(this: ?, args?: ?) -> !this.!ret",
|
||||
"!effects": [
|
||||
"call and return !this this=!0 !1 !2 !3 !4"
|
||||
],
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/call",
|
||||
"!doc": "Calls a function with a given this value and arguments provided individually."
|
||||
},
|
||||
"bind": {
|
||||
"!type": "fn(this: ?, args?: ?) -> !custom:Function_bind",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind",
|
||||
"!doc": "Creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function was called."
|
||||
},
|
||||
"prototype": "?"
|
||||
},
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function",
|
||||
"!doc": "Every function in JavaScript is actually a Function object."
|
||||
},
|
||||
"Array": {
|
||||
"!type": "fn(size: number) -> !custom:Array_ctor",
|
||||
"isArray": {
|
||||
"!type": "fn(value: ?) -> bool",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/isArray",
|
||||
"!doc": "Returns true if an object is an array, false if it is not."
|
||||
},
|
||||
"prototype": {
|
||||
"!stdProto": "Array",
|
||||
"length": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/length",
|
||||
"!doc": "An unsigned, 32-bit integer that specifies the number of elements in an array."
|
||||
},
|
||||
"concat": {
|
||||
"!type": "fn(other: [?]) -> !this",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/concat",
|
||||
"!doc": "Returns a new array comprised of this array joined with other array(s) and/or value(s)."
|
||||
},
|
||||
"join": {
|
||||
"!type": "fn(separator?: string) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/join",
|
||||
"!doc": "Joins all elements of an array into a string."
|
||||
},
|
||||
"splice": {
|
||||
"!type": "fn(pos: number, amount: number)",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/splice",
|
||||
"!doc": "Changes the content of an array, adding new elements while removing old elements."
|
||||
},
|
||||
"pop": {
|
||||
"!type": "fn() -> !this.<i>",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/pop",
|
||||
"!doc": "Removes the last element from an array and returns that element."
|
||||
},
|
||||
"push": {
|
||||
"!type": "fn(newelt: ?) -> number",
|
||||
"!effects": [
|
||||
"propagate !0 !this.<i>"
|
||||
],
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/push",
|
||||
"!doc": "Mutates an array by appending the given elements and returning the new length of the array."
|
||||
},
|
||||
"shift": {
|
||||
"!type": "fn() -> !this.<i>",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/shift",
|
||||
"!doc": "Removes the first element from an array and returns that element. This method changes the length of the array."
|
||||
},
|
||||
"unshift": {
|
||||
"!type": "fn(newelt: ?) -> number",
|
||||
"!effects": [
|
||||
"propagate !0 !this.<i>"
|
||||
],
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/unshift",
|
||||
"!doc": "Adds one or more elements to the beginning of an array and returns the new length of the array."
|
||||
},
|
||||
"slice": {
|
||||
"!type": "fn(from: number, to?: number) -> !this",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/slice",
|
||||
"!doc": "Returns a shallow copy of a portion of an array."
|
||||
},
|
||||
"reverse": {
|
||||
"!type": "fn()",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/reverse",
|
||||
"!doc": "Reverses an array in place. The first array element becomes the last and the last becomes the first."
|
||||
},
|
||||
"sort": {
|
||||
"!type": "fn(compare?: fn(a: ?, b: ?) -> number)",
|
||||
"!effects": [
|
||||
"call !0 !this.<i> !this.<i>"
|
||||
],
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/sort",
|
||||
"!doc": "Sorts the elements of an array in place and returns the array."
|
||||
},
|
||||
"indexOf": {
|
||||
"!type": "fn(elt: ?, from?: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf",
|
||||
"!doc": "Returns the first index at which a given element can be found in the array, or -1 if it is not present."
|
||||
},
|
||||
"lastIndexOf": {
|
||||
"!type": "fn(elt: ?, from?: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/lastIndexOf",
|
||||
"!doc": "Returns the last index at which a given element can be found in the array, or -1 if it is not present. The array is searched backwards, starting at fromIndex."
|
||||
},
|
||||
"every": {
|
||||
"!type": "fn(test: fn(elt: ?, i: number) -> bool, context?: ?) -> bool",
|
||||
"!effects": [
|
||||
"call !0 this=!1 !this.<i> number"
|
||||
],
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/every",
|
||||
"!doc": "Tests whether all elements in the array pass the test implemented by the provided function."
|
||||
},
|
||||
"some": {
|
||||
"!type": "fn(test: fn(elt: ?, i: number) -> bool, context?: ?) -> bool",
|
||||
"!effects": [
|
||||
"call !0 this=!1 !this.<i> number"
|
||||
],
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/some",
|
||||
"!doc": "Tests whether some element in the array passes the test implemented by the provided function."
|
||||
},
|
||||
"filter": {
|
||||
"!type": "fn(test: fn(elt: ?, i: number) -> bool, context?: ?) -> !this",
|
||||
"!effects": [
|
||||
"call !0 this=!1 !this.<i> number"
|
||||
],
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/filter",
|
||||
"!doc": "Creates a new array with all elements that pass the test implemented by the provided function."
|
||||
},
|
||||
"forEach": {
|
||||
"!type": "fn(f: fn(elt: ?, i: number), context?: ?)",
|
||||
"!effects": [
|
||||
"call !0 this=!1 !this.<i> number"
|
||||
],
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach",
|
||||
"!doc": "Executes a provided function once per array element."
|
||||
},
|
||||
"map": {
|
||||
"!type": "fn(f: fn(elt: ?, i: number) -> ?, context?: ?) -> [!0.!ret]",
|
||||
"!effects": [
|
||||
"call !0 this=!1 !this.<i> number"
|
||||
],
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/map",
|
||||
"!doc": "Creates a new array with the results of calling a provided function on every element in this array."
|
||||
},
|
||||
"reduce": {
|
||||
"!type": "fn(combine: fn(sum: ?, elt: ?, i: number) -> ?, init?: ?) -> !0.!ret",
|
||||
"!effects": [
|
||||
"call !0 !1 !this.<i> number"
|
||||
],
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/Reduce",
|
||||
"!doc": "Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value."
|
||||
},
|
||||
"reduceRight": {
|
||||
"!type": "fn(combine: fn(sum: ?, elt: ?, i: number) -> ?, init?: ?) -> !0.!ret",
|
||||
"!effects": [
|
||||
"call !0 !1 !this.<i> number"
|
||||
],
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/ReduceRight",
|
||||
"!doc": "Apply a function simultaneously against two values of the array (from right-to-left) as to reduce it to a single value."
|
||||
}
|
||||
},
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array",
|
||||
"!doc": "The JavaScript Array global object is a constructor for arrays, which are high-level, list-like objects."
|
||||
},
|
||||
"String": {
|
||||
"!type": "fn(value: ?) -> string",
|
||||
"fromCharCode": {
|
||||
"!type": "fn(code: number) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/fromCharCode",
|
||||
"!doc": "Returns a string created by using the specified sequence of Unicode values."
|
||||
},
|
||||
"prototype": {
|
||||
"!stdProto": "String",
|
||||
"length": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en/docs/JavaScript/Reference/Global_Objects/String/length",
|
||||
"!doc": "Represents the length of a string."
|
||||
},
|
||||
"<i>": "string",
|
||||
"charAt": {
|
||||
"!type": "fn(i: number) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/charAt",
|
||||
"!doc": "Returns the specified character from a string."
|
||||
},
|
||||
"charCodeAt": {
|
||||
"!type": "fn(i: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/charCodeAt",
|
||||
"!doc": "Returns the numeric Unicode value of the character at the given index (except for unicode codepoints > 0x10000)."
|
||||
},
|
||||
"indexOf": {
|
||||
"!type": "fn(char: string, from?: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/indexOf",
|
||||
"!doc": "Returns the index within the calling String object of the first occurrence of the specified value, starting the search at fromIndex,\nreturns -1 if the value is not found."
|
||||
},
|
||||
"lastIndexOf": {
|
||||
"!type": "fn(char: string, from?: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/lastIndexOf",
|
||||
"!doc": "Returns the index within the calling String object of the last occurrence of the specified value, or -1 if not found. The calling string is searched backward, starting at fromIndex."
|
||||
},
|
||||
"substring": {
|
||||
"!type": "fn(from: number, to?: number) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/substring",
|
||||
"!doc": "Returns a subset of a string between one index and another, or through the end of the string."
|
||||
},
|
||||
"substr": {
|
||||
"!type": "fn(from: number, length?: number) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/substr",
|
||||
"!doc": "Returns the characters in a string beginning at the specified location through the specified number of characters."
|
||||
},
|
||||
"slice": {
|
||||
"!type": "fn(from: number, to?: number) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/slice",
|
||||
"!doc": "Extracts a section of a string and returns a new string."
|
||||
},
|
||||
"trim": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/Trim",
|
||||
"!doc": "Removes whitespace from both ends of the string."
|
||||
},
|
||||
"trimLeft": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/TrimLeft",
|
||||
"!doc": "Removes whitespace from the left end of the string."
|
||||
},
|
||||
"trimRight": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/TrimRight",
|
||||
"!doc": "Removes whitespace from the right end of the string."
|
||||
},
|
||||
"toUpperCase": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toUpperCase",
|
||||
"!doc": "Returns the calling string value converted to uppercase."
|
||||
},
|
||||
"toLowerCase": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toLowerCase",
|
||||
"!doc": "Returns the calling string value converted to lowercase."
|
||||
},
|
||||
"toLocaleUpperCase": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toLocaleUpperCase",
|
||||
"!doc": "Returns the calling string value converted to upper case, according to any locale-specific case mappings."
|
||||
},
|
||||
"toLocaleLowerCase": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toLocaleLowerCase",
|
||||
"!doc": "Returns the calling string value converted to lower case, according to any locale-specific case mappings."
|
||||
},
|
||||
"split": {
|
||||
"!type": "fn(pattern: string) -> [string]",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/split",
|
||||
"!doc": "Splits a String object into an array of strings by separating the string into substrings."
|
||||
},
|
||||
"concat": {
|
||||
"!type": "fn(other: string) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/concat",
|
||||
"!doc": "Combines the text of two or more strings and returns a new string."
|
||||
},
|
||||
"localeCompare": {
|
||||
"!type": "fn(other: string) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/localeCompare",
|
||||
"!doc": "Returns a number indicating whether a reference string comes before or after or is the same as the given string in sort order."
|
||||
},
|
||||
"match": {
|
||||
"!type": "fn(pattern: +RegExp) -> [string]",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/match",
|
||||
"!doc": "Used to retrieve the matches when matching a string against a regular expression."
|
||||
},
|
||||
"replace": {
|
||||
"!type": "fn(pattern: +RegExp, replacement: string) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/replace",
|
||||
"!doc": "Returns a new string with some or all matches of a pattern replaced by a replacement. The pattern can be a string or a RegExp, and the replacement can be a string or a function to be called for each match."
|
||||
},
|
||||
"search": {
|
||||
"!type": "fn(pattern: +RegExp) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/search",
|
||||
"!doc": "Executes the search for a match between a regular expression and this String object."
|
||||
}
|
||||
},
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String",
|
||||
"!doc": "The String global object is a constructor for strings, or a sequence of characters."
|
||||
},
|
||||
"Number": {
|
||||
"!type": "fn(value: ?) -> number",
|
||||
"MAX_VALUE": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/MAX_VALUE",
|
||||
"!doc": "The maximum numeric value representable in JavaScript."
|
||||
},
|
||||
"MIN_VALUE": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/MIN_VALUE",
|
||||
"!doc": "The smallest positive numeric value representable in JavaScript."
|
||||
},
|
||||
"POSITIVE_INFINITY": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/POSITIVE_INFINITY",
|
||||
"!doc": "A value representing the positive Infinity value."
|
||||
},
|
||||
"NEGATIVE_INFINITY": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/NEGATIVE_INFINITY",
|
||||
"!doc": "A value representing the negative Infinity value."
|
||||
},
|
||||
"prototype": {
|
||||
"!stdProto": "Number",
|
||||
"toString": {
|
||||
"!type": "fn(radix?: number) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/toString",
|
||||
"!doc": "Returns a string representing the specified Number object"
|
||||
},
|
||||
"toFixed": {
|
||||
"!type": "fn(digits: number) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/toFixed",
|
||||
"!doc": "Formats a number using fixed-point notation"
|
||||
},
|
||||
"toExponential": {
|
||||
"!type": "fn(digits: number) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/toExponential",
|
||||
"!doc": "Returns a string representing the Number object in exponential notation"
|
||||
}
|
||||
},
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number",
|
||||
"!doc": "The Number JavaScript object is a wrapper object allowing you to work with numerical values. A Number object is created using the Number() constructor."
|
||||
},
|
||||
"Boolean": {
|
||||
"!type": "fn(value: ?) -> bool",
|
||||
"prototype": {
|
||||
"!stdProto": "Boolean"
|
||||
},
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Boolean",
|
||||
"!doc": "The Boolean object is an object wrapper for a boolean value."
|
||||
},
|
||||
"RegExp": {
|
||||
"!type": "fn(source: string, flags?: string)",
|
||||
"prototype": {
|
||||
"!stdProto": "RegExp",
|
||||
"exec": {
|
||||
"!type": "fn(input: string) -> [string]",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/exec",
|
||||
"!doc": "Executes a search for a match in a specified string. Returns a result array, or null."
|
||||
},
|
||||
"compile": {
|
||||
"!type": "fn(source: string, flags?: string)",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp",
|
||||
"!doc": "Creates a regular expression object for matching text with a pattern."
|
||||
},
|
||||
"test": {
|
||||
"!type": "fn(input: string) -> bool",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/test",
|
||||
"!doc": "Executes the search for a match between a regular expression and a specified string. Returns true or false."
|
||||
},
|
||||
"global": {
|
||||
"!type": "bool",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp",
|
||||
"!doc": "Creates a regular expression object for matching text with a pattern."
|
||||
},
|
||||
"ignoreCase": {
|
||||
"!type": "bool",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp",
|
||||
"!doc": "Creates a regular expression object for matching text with a pattern."
|
||||
},
|
||||
"multiline": {
|
||||
"!type": "bool",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/multiline",
|
||||
"!doc": "Reflects whether or not to search in strings across multiple lines.\n"
|
||||
},
|
||||
"source": {
|
||||
"!type": "string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/source",
|
||||
"!doc": "A read-only property that contains the text of the pattern, excluding the forward slashes.\n"
|
||||
},
|
||||
"lastIndex": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/lastIndex",
|
||||
"!doc": "A read/write integer property that specifies the index at which to start the next match."
|
||||
}
|
||||
},
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp",
|
||||
"!doc": "Creates a regular expression object for matching text with a pattern."
|
||||
},
|
||||
"Date": {
|
||||
"!type": "fn(ms: number)",
|
||||
"parse": {
|
||||
"!type": "fn(source: string) -> +Date",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/parse",
|
||||
"!doc": "Parses a string representation of a date, and returns the number of milliseconds since January 1, 1970, 00:00:00 UTC."
|
||||
},
|
||||
"UTC": {
|
||||
"!type": "fn(year: number, month: number, date: number, hour?: number, min?: number, sec?: number, ms?: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/UTC",
|
||||
"!doc": "Accepts the same parameters as the longest form of the constructor, and returns the number of milliseconds in a Date object since January 1, 1970, 00:00:00, universal time."
|
||||
},
|
||||
"now": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/now",
|
||||
"!doc": "Returns the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC."
|
||||
},
|
||||
"prototype": {
|
||||
"toUTCString": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toUTCString",
|
||||
"!doc": "Converts a date to a string, using the universal time convention."
|
||||
},
|
||||
"toISOString": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toISOString",
|
||||
"!doc": "JavaScript provides a direct way to convert a date object into a string in ISO format, the ISO 8601 Extended Format."
|
||||
},
|
||||
"toDateString": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toDateString",
|
||||
"!doc": "Returns the date portion of a Date object in human readable form in American English."
|
||||
},
|
||||
"toTimeString": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toTimeString",
|
||||
"!doc": "Returns the time portion of a Date object in human readable form in American English."
|
||||
},
|
||||
"toLocaleDateString": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toLocaleDateString",
|
||||
"!doc": "Converts a date to a string, returning the \"date\" portion using the operating system's locale's conventions.\n"
|
||||
},
|
||||
"toLocaleTimeString": {
|
||||
"!type": "fn() -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toLocaleTimeString",
|
||||
"!doc": "Converts a date to a string, returning the \"time\" portion using the current locale's conventions."
|
||||
},
|
||||
"getTime": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getTime",
|
||||
"!doc": "Returns the numeric value corresponding to the time for the specified date according to universal time."
|
||||
},
|
||||
"getFullYear": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getFullYear",
|
||||
"!doc": "Returns the year of the specified date according to local time."
|
||||
},
|
||||
"getYear": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getYear",
|
||||
"!doc": "Returns the year in the specified date according to local time."
|
||||
},
|
||||
"getMonth": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getMonth",
|
||||
"!doc": "Returns the month in the specified date according to local time."
|
||||
},
|
||||
"getUTCMonth": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCMonth",
|
||||
"!doc": "Returns the month of the specified date according to universal time.\n"
|
||||
},
|
||||
"getDate": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getDate",
|
||||
"!doc": "Returns the day of the month for the specified date according to local time."
|
||||
},
|
||||
"getUTCDate": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCDate",
|
||||
"!doc": "Returns the day (date) of the month in the specified date according to universal time.\n"
|
||||
},
|
||||
"getDay": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getDay",
|
||||
"!doc": "Returns the day of the week for the specified date according to local time."
|
||||
},
|
||||
"getUTCDay": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCDay",
|
||||
"!doc": "Returns the day of the week in the specified date according to universal time.\n"
|
||||
},
|
||||
"getHours": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getHours",
|
||||
"!doc": "Returns the hour for the specified date according to local time."
|
||||
},
|
||||
"getUTCHours": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCHours",
|
||||
"!doc": "Returns the hours in the specified date according to universal time.\n"
|
||||
},
|
||||
"getMinutes": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getMinutes",
|
||||
"!doc": "Returns the minutes in the specified date according to local time."
|
||||
},
|
||||
"getUTCMinutes": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date",
|
||||
"!doc": "Creates JavaScript Date instances which let you work with dates and times."
|
||||
},
|
||||
"getSeconds": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getSeconds",
|
||||
"!doc": "Returns the seconds in the specified date according to local time."
|
||||
},
|
||||
"getUTCSeconds": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCSeconds",
|
||||
"!doc": "Returns the seconds in the specified date according to universal time.\n"
|
||||
},
|
||||
"getMilliseconds": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getMilliseconds",
|
||||
"!doc": "Returns the milliseconds in the specified date according to local time."
|
||||
},
|
||||
"getUTCMilliseconds": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCMilliseconds",
|
||||
"!doc": "Returns the milliseconds in the specified date according to universal time.\n"
|
||||
},
|
||||
"getTimezoneOffset": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset",
|
||||
"!doc": "Returns the time-zone offset from UTC, in minutes, for the current locale."
|
||||
},
|
||||
"setTime": {
|
||||
"!type": "fn(date: +Date) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setTime",
|
||||
"!doc": "Sets the Date object to the time represented by a number of milliseconds since January 1, 1970, 00:00:00 UTC.\n"
|
||||
},
|
||||
"setFullYear": {
|
||||
"!type": "fn(year: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setFullYear",
|
||||
"!doc": "Sets the full year for a specified date according to local time.\n"
|
||||
},
|
||||
"setUTCFullYear": {
|
||||
"!type": "fn(year: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCFullYear",
|
||||
"!doc": "Sets the full year for a specified date according to universal time.\n"
|
||||
},
|
||||
"setMonth": {
|
||||
"!type": "fn(month: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setMonth",
|
||||
"!doc": "Set the month for a specified date according to local time."
|
||||
},
|
||||
"setUTCMonth": {
|
||||
"!type": "fn(month: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCMonth",
|
||||
"!doc": "Sets the month for a specified date according to universal time.\n"
|
||||
},
|
||||
"setDate": {
|
||||
"!type": "fn(day: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setDate",
|
||||
"!doc": "Sets the day of the month for a specified date according to local time."
|
||||
},
|
||||
"setUTCDate": {
|
||||
"!type": "fn(day: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCDate",
|
||||
"!doc": "Sets the day of the month for a specified date according to universal time.\n"
|
||||
},
|
||||
"setHours": {
|
||||
"!type": "fn(hour: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setHours",
|
||||
"!doc": "Sets the hours for a specified date according to local time, and returns the number of milliseconds since 1 January 1970 00:00:00 UTC until the time represented by the updated Date instance."
|
||||
},
|
||||
"setUTCHours": {
|
||||
"!type": "fn(hour: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCHours",
|
||||
"!doc": "Sets the hour for a specified date according to universal time.\n"
|
||||
},
|
||||
"setMinutes": {
|
||||
"!type": "fn(min: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setMinutes",
|
||||
"!doc": "Sets the minutes for a specified date according to local time."
|
||||
},
|
||||
"setUTCMinutes": {
|
||||
"!type": "fn(min: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCMinutes",
|
||||
"!doc": "Sets the minutes for a specified date according to universal time.\n"
|
||||
},
|
||||
"setSeconds": {
|
||||
"!type": "fn(sec: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setSeconds",
|
||||
"!doc": "Sets the seconds for a specified date according to local time."
|
||||
},
|
||||
"setUTCSeconds": {
|
||||
"!type": "fn(sec: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCSeconds",
|
||||
"!doc": "Sets the seconds for a specified date according to universal time.\n"
|
||||
},
|
||||
"setMilliseconds": {
|
||||
"!type": "fn(ms: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setMilliseconds",
|
||||
"!doc": "Sets the milliseconds for a specified date according to local time.\n"
|
||||
},
|
||||
"setUTCMilliseconds": {
|
||||
"!type": "fn(ms: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCMilliseconds",
|
||||
"!doc": "Sets the milliseconds for a specified date according to universal time.\n"
|
||||
}
|
||||
},
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date",
|
||||
"!doc": "Creates JavaScript Date instances which let you work with dates and times."
|
||||
},
|
||||
"Error": {
|
||||
"!type": "fn(message: string)",
|
||||
"prototype": {
|
||||
"name": {
|
||||
"!type": "string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error/name",
|
||||
"!doc": "A name for the type of error."
|
||||
},
|
||||
"message": {
|
||||
"!type": "string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error/message",
|
||||
"!doc": "A human-readable description of the error."
|
||||
}
|
||||
},
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error",
|
||||
"!doc": "Creates an error object."
|
||||
},
|
||||
"SyntaxError": {
|
||||
"!type": "fn(message: string)",
|
||||
"prototype": "Error.prototype",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/SyntaxError",
|
||||
"!doc": "Represents an error when trying to interpret syntactically invalid code."
|
||||
},
|
||||
"ReferenceError": {
|
||||
"!type": "fn(message: string)",
|
||||
"prototype": "Error.prototype",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/ReferenceError",
|
||||
"!doc": "Represents an error when a non-existent variable is referenced."
|
||||
},
|
||||
"URIError": {
|
||||
"!type": "fn(message: string)",
|
||||
"prototype": "Error.prototype",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/URIError",
|
||||
"!doc": "Represents an error when a malformed URI is encountered."
|
||||
},
|
||||
"EvalError": {
|
||||
"!type": "fn(message: string)",
|
||||
"prototype": "Error.prototype",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/EvalError",
|
||||
"!doc": "Represents an error regarding the eval function."
|
||||
},
|
||||
"RangeError": {
|
||||
"!type": "fn(message: string)",
|
||||
"prototype": "Error.prototype",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RangeError",
|
||||
"!doc": "Represents an error when a number is not within the correct range allowed."
|
||||
},
|
||||
"parseInt": {
|
||||
"!type": "fn(string: string, radix?: number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/parseInt",
|
||||
"!doc": "Parses a string argument and returns an integer of the specified radix or base."
|
||||
},
|
||||
"parseFloat": {
|
||||
"!type": "fn(string: string) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/parseFloat",
|
||||
"!doc": "Parses a string argument and returns a floating point number."
|
||||
},
|
||||
"isNaN": {
|
||||
"!type": "fn(value: number) -> bool",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/isNaN",
|
||||
"!doc": "Determines whether a value is NaN or not. Be careful, this function is broken. You may be interested in ECMAScript 6 Number.isNaN."
|
||||
},
|
||||
"eval": {
|
||||
"!type": "fn(code: string) -> ?",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/eval",
|
||||
"!doc": "Evaluates JavaScript code represented as a string."
|
||||
},
|
||||
"encodeURI": {
|
||||
"!type": "fn(uri: string) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURI",
|
||||
"!doc": "Encodes a Uniform Resource Identifier (URI) by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character (will only be four escape sequences for characters composed of two \"surrogate\" characters)."
|
||||
},
|
||||
"encodeURIComponent": {
|
||||
"!type": "fn(uri: string) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent",
|
||||
"!doc": "Encodes a Uniform Resource Identifier (URI) component by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character (will only be four escape sequences for characters composed of two \"surrogate\" characters)."
|
||||
},
|
||||
"decodeURI": {
|
||||
"!type": "fn(uri: string) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/decodeURI",
|
||||
"!doc": "Decodes a Uniform Resource Identifier (URI) previously created by encodeURI or by a similar routine."
|
||||
},
|
||||
"decodeURIComponent": {
|
||||
"!type": "fn(uri: string) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/decodeURIComponent",
|
||||
"!doc": "Decodes a Uniform Resource Identifier (URI) component previously created by encodeURIComponent or by a similar routine."
|
||||
},
|
||||
"Math": {
|
||||
"E": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/E",
|
||||
"!doc": "The base of natural logarithms, e, approximately 2.718."
|
||||
},
|
||||
"LN2": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/LN2",
|
||||
"!doc": "The natural logarithm of 2, approximately 0.693."
|
||||
},
|
||||
"LN10": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/LN10",
|
||||
"!doc": "The natural logarithm of 10, approximately 2.302."
|
||||
},
|
||||
"LOG2E": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/LOG2E",
|
||||
"!doc": "The base 2 logarithm of E (approximately 1.442)."
|
||||
},
|
||||
"LOG10E": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/LOG10E",
|
||||
"!doc": "The base 10 logarithm of E (approximately 0.434)."
|
||||
},
|
||||
"SQRT1_2": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/SQRT1_2",
|
||||
"!doc": "The square root of 1/2; equivalently, 1 over the square root of 2, approximately 0.707."
|
||||
},
|
||||
"SQRT2": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/SQRT2",
|
||||
"!doc": "The square root of 2, approximately 1.414."
|
||||
},
|
||||
"PI": {
|
||||
"!type": "number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/PI",
|
||||
"!doc": "The ratio of the circumference of a circle to its diameter, approximately 3.14159."
|
||||
},
|
||||
"abs": {
|
||||
"!type": "fn(number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/abs",
|
||||
"!doc": "Returns the absolute value of a number."
|
||||
},
|
||||
"cos": {
|
||||
"!type": "fn(number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/cos",
|
||||
"!doc": "Returns the cosine of a number."
|
||||
},
|
||||
"sin": {
|
||||
"!type": "fn(number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/sin",
|
||||
"!doc": "Returns the sine of a number."
|
||||
},
|
||||
"tan": {
|
||||
"!type": "fn(number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/tan",
|
||||
"!doc": "Returns the tangent of a number."
|
||||
},
|
||||
"acos": {
|
||||
"!type": "fn(number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/acos",
|
||||
"!doc": "Returns the arccosine (in radians) of a number."
|
||||
},
|
||||
"asin": {
|
||||
"!type": "fn(number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/asin",
|
||||
"!doc": "Returns the arcsine (in radians) of a number."
|
||||
},
|
||||
"atan": {
|
||||
"!type": "fn(number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/atan",
|
||||
"!doc": "Returns the arctangent (in radians) of a number."
|
||||
},
|
||||
"atan2": {
|
||||
"!type": "fn(number, number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/atan2",
|
||||
"!doc": "Returns the arctangent of the quotient of its arguments."
|
||||
},
|
||||
"ceil": {
|
||||
"!type": "fn(number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/ceil",
|
||||
"!doc": "Returns the smallest integer greater than or equal to a number."
|
||||
},
|
||||
"floor": {
|
||||
"!type": "fn(number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/floor",
|
||||
"!doc": "Returns the largest integer less than or equal to a number."
|
||||
},
|
||||
"round": {
|
||||
"!type": "fn(number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/round",
|
||||
"!doc": "Returns the value of a number rounded to the nearest integer."
|
||||
},
|
||||
"exp": {
|
||||
"!type": "fn(number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/exp",
|
||||
"!doc": "Returns Ex, where x is the argument, and E is Euler's constant, the base of the natural logarithms."
|
||||
},
|
||||
"log": {
|
||||
"!type": "fn(number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/log",
|
||||
"!doc": "Returns the natural logarithm (base E) of a number."
|
||||
},
|
||||
"sqrt": {
|
||||
"!type": "fn(number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/sqrt",
|
||||
"!doc": "Returns the square root of a number."
|
||||
},
|
||||
"pow": {
|
||||
"!type": "fn(number, number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/pow",
|
||||
"!doc": "Returns base to the exponent power, that is, baseexponent."
|
||||
},
|
||||
"max": {
|
||||
"!type": "fn(number, number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/max",
|
||||
"!doc": "Returns the largest of zero or more numbers."
|
||||
},
|
||||
"min": {
|
||||
"!type": "fn(number, number) -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/min",
|
||||
"!doc": "Returns the smallest of zero or more numbers."
|
||||
},
|
||||
"random": {
|
||||
"!type": "fn() -> number",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random",
|
||||
"!doc": "Returns a floating-point, pseudo-random number in the range [0, 1) that is, from 0 (inclusive) up to but not including 1 (exclusive), which you can then scale to your desired range."
|
||||
},
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math",
|
||||
"!doc": "A built-in object that has properties and methods for mathematical constants and functions."
|
||||
},
|
||||
"JSON": {
|
||||
"parse": {
|
||||
"!type": "fn(json: string) -> ?",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/parse",
|
||||
"!doc": "Parse a string as JSON, optionally transforming the value produced by parsing."
|
||||
},
|
||||
"stringify": {
|
||||
"!type": "fn(value: ?) -> string",
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/stringify",
|
||||
"!doc": "Convert a value to JSON, optionally replacing values if a replacer function is specified, or optionally including only the specified properties if a replacer array is specified."
|
||||
},
|
||||
"!url": "https://developer.mozilla.org/en-US/docs/JSON",
|
||||
"!doc": "JSON (JavaScript Object Notation) is a data-interchange format. It closely resembles a subset of JavaScript syntax, although it is not a strict subset. (See JSON in the JavaScript Reference for full details.) It is useful when writing any kind of JavaScript-based application, including websites and browser extensions. For example, you might store user information in JSON format in a cookie, or you might store extension preferences in JSON in a string-valued browser preference."
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,20 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
|
||||
|
||||
JS_MODULES_PATH = 'modules/devtools/tern'
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'browser.js',
|
||||
'comment.js',
|
||||
'condense.js',
|
||||
'def.js',
|
||||
'ecma5.js',
|
||||
'infer.js',
|
||||
'signal.js',
|
||||
'tern.js',
|
||||
]
|
|
@ -0,0 +1,26 @@
|
|||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
return mod(exports);
|
||||
if (typeof define == "function" && define.amd) // AMD
|
||||
return define(["exports"], mod);
|
||||
mod((self.tern || (self.tern = {})).signal = {}); // Plain browser env
|
||||
})(function(exports) {
|
||||
function on(type, f) {
|
||||
var handlers = this._handlers || (this._handlers = Object.create(null));
|
||||
(handlers[type] || (handlers[type] = [])).push(f);
|
||||
}
|
||||
function off(type, f) {
|
||||
var arr = this._handlers && this._handlers[type];
|
||||
if (arr) for (var i = 0; i < arr.length; ++i)
|
||||
if (arr[i] == f) { arr.splice(i, 1); break; }
|
||||
}
|
||||
function signal(type, a1, a2, a3, a4) {
|
||||
var arr = this._handlers && this._handlers[type];
|
||||
if (arr) for (var i = 0; i < arr.length; ++i) arr[i].call(this, a1, a2, a3, a4);
|
||||
}
|
||||
|
||||
exports.mixin = function(obj) {
|
||||
obj.on = on; obj.off = off; obj.signal = signal;
|
||||
return obj;
|
||||
};
|
||||
});
|
|
@ -0,0 +1,814 @@
|
|||
// The Tern server object
|
||||
|
||||
// A server is a stateful object that manages the analysis for a
|
||||
// project, and defines an interface for querying the code in the
|
||||
// project.
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
return mod(exports, require("./infer"), require("./signal"),
|
||||
require("acorn/acorn"), require("acorn/util/walk"));
|
||||
if (typeof define == "function" && define.amd) // AMD
|
||||
return define(["exports", "./infer", "./signal", "acorn/acorn", "acorn/util/walk"], mod);
|
||||
mod(self.tern || (self.tern = {}), tern, tern.signal, acorn, acorn.walk); // Plain browser env
|
||||
})(function(exports, infer, signal, acorn, walk) {
|
||||
"use strict";
|
||||
|
||||
var plugins = Object.create(null);
|
||||
exports.registerPlugin = function(name, init) { plugins[name] = init; };
|
||||
|
||||
var defaultOptions = {
|
||||
debug: false,
|
||||
async: false,
|
||||
getFile: function(_f, c) { if (this.async) c(null, null); },
|
||||
defs: [],
|
||||
plugins: {},
|
||||
fetchTimeout: 1000
|
||||
};
|
||||
|
||||
var queryTypes = {
|
||||
completions: {
|
||||
takesFile: true,
|
||||
run: findCompletions
|
||||
},
|
||||
properties: {
|
||||
run: findProperties
|
||||
},
|
||||
type: {
|
||||
takesFile: true,
|
||||
run: findTypeAt
|
||||
},
|
||||
documentation: {
|
||||
takesFile: true,
|
||||
run: findDocs
|
||||
},
|
||||
definition: {
|
||||
takesFile: true,
|
||||
run: findDef
|
||||
},
|
||||
refs: {
|
||||
takesFile: true,
|
||||
fullFile: true,
|
||||
run: findRefs
|
||||
},
|
||||
rename: {
|
||||
takesFile: true,
|
||||
fullFile: true,
|
||||
run: buildRename
|
||||
},
|
||||
files: {
|
||||
run: listFiles
|
||||
}
|
||||
};
|
||||
|
||||
exports.defineQueryType = function(name, desc) { queryTypes[name] = desc; };
|
||||
|
||||
function File(name) {
|
||||
this.name = name;
|
||||
this.scope = this.text = this.ast = this.lineOffsets = null;
|
||||
}
|
||||
File.prototype.asLineChar = function(pos) { return asLineChar(this, pos); };
|
||||
|
||||
function updateText(file, text, srv) {
|
||||
file.text = text;
|
||||
file.ast = infer.parse(text, srv.passes, {directSourceFile: file});
|
||||
file.lineOffsets = null;
|
||||
}
|
||||
|
||||
var Server = exports.Server = function(options) {
|
||||
this.cx = null;
|
||||
this.options = options || {};
|
||||
for (var o in defaultOptions) if (!options.hasOwnProperty(o))
|
||||
options[o] = defaultOptions[o];
|
||||
|
||||
this.handlers = Object.create(null);
|
||||
this.files = [];
|
||||
this.uses = 0;
|
||||
this.pending = 0;
|
||||
this.asyncError = null;
|
||||
this.passes = Object.create(null);
|
||||
|
||||
this.defs = options.defs.slice(0);
|
||||
for (var plugin in options.plugins) if (options.plugins.hasOwnProperty(plugin) && plugin in plugins) {
|
||||
var init = plugins[plugin](this, options.plugins[plugin]);
|
||||
if (init && init.defs) {
|
||||
if (init.loadFirst) this.defs.unshift(init.defs);
|
||||
else this.defs.push(init.defs);
|
||||
}
|
||||
if (init && init.passes) for (var type in init.passes) if (init.passes.hasOwnProperty(type))
|
||||
(this.passes[type] || (this.passes[type] = [])).push(init.passes[type]);
|
||||
}
|
||||
|
||||
this.reset();
|
||||
};
|
||||
Server.prototype = signal.mixin({
|
||||
addFile: function(name, /*optional*/ text) {
|
||||
ensureFile(this, name, text);
|
||||
},
|
||||
delFile: function(name) {
|
||||
for (var i = 0, f; i < this.files.length; ++i) if ((f = this.files[i]).name == name) {
|
||||
clearFile(this, f);
|
||||
this.files.splice(i--, 1);
|
||||
return;
|
||||
}
|
||||
},
|
||||
reset: function() {
|
||||
this.signal("reset");
|
||||
this.cx = new infer.Context(this.defs, this);
|
||||
this.uses = 0;
|
||||
for (var i = 0; i < this.files.length; ++i) {
|
||||
var file = this.files[i];
|
||||
file.scope = null;
|
||||
}
|
||||
},
|
||||
|
||||
request: function(doc, c) {
|
||||
var inv = invalidDoc(doc);
|
||||
if (inv) return c(inv);
|
||||
|
||||
var self = this;
|
||||
doRequest(this, doc, function(err, data) {
|
||||
c(err, data);
|
||||
if (self.uses > 40) {
|
||||
self.reset();
|
||||
analyzeAll(self, function(){});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
findFile: function(name) {
|
||||
return findFile(this.files, name);
|
||||
},
|
||||
|
||||
flush: function(c) {
|
||||
var cx = this.cx;
|
||||
analyzeAll(this, function(err) {
|
||||
if (err) return c(err);
|
||||
infer.withContext(cx, c);
|
||||
});
|
||||
},
|
||||
|
||||
startAsyncAction: function() {
|
||||
++this.pending;
|
||||
},
|
||||
finishAsyncAction: function(err) {
|
||||
if (err) this.asyncError = err;
|
||||
if (--this.pending == 0) this.signal("everythingFetched");
|
||||
}
|
||||
});
|
||||
|
||||
function doRequest(srv, doc, c) {
|
||||
if (doc.query && !queryTypes.hasOwnProperty(doc.query.type))
|
||||
return c("No query type '" + doc.query.type + "' defined");
|
||||
|
||||
var query = doc.query;
|
||||
// Respond as soon as possible when this just uploads files
|
||||
if (!query) c(null, {});
|
||||
|
||||
var files = doc.files || [];
|
||||
if (files.length) ++srv.uses;
|
||||
for (var i = 0; i < files.length; ++i) {
|
||||
var file = files[i];
|
||||
ensureFile(srv, file.name, file.type == "full" ? file.text : null);
|
||||
}
|
||||
|
||||
if (!query) {
|
||||
analyzeAll(srv, function(){});
|
||||
return;
|
||||
}
|
||||
|
||||
var queryType = queryTypes[query.type];
|
||||
if (queryType.takesFile) {
|
||||
if (typeof query.file != "string") return c(".query.file must be a string");
|
||||
if (!/^#/.test(query.file)) ensureFile(srv, query.file);
|
||||
}
|
||||
|
||||
analyzeAll(srv, function(err) {
|
||||
if (err) return c(err);
|
||||
var file = queryType.takesFile && resolveFile(srv, files, query.file);
|
||||
if (queryType.fullFile && file.type == "part")
|
||||
return c("Can't run a " + query.type + " query on a file fragment");
|
||||
|
||||
infer.withContext(srv.cx, function() {
|
||||
var result;
|
||||
try {
|
||||
result = queryType.run(srv, query, file);
|
||||
} catch (e) {
|
||||
if (srv.options.debug && e.name != "TernError") console.error(e.stack);
|
||||
return c(e);
|
||||
}
|
||||
c(null, result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function analyzeFile(srv, file) {
|
||||
infer.withContext(srv.cx, function() {
|
||||
file.scope = srv.cx.topScope;
|
||||
srv.signal("beforeLoad", file);
|
||||
infer.markVariablesDefinedBy(file.scope, file.name);
|
||||
infer.analyze(file.ast, file.name, file.scope, srv.passes);
|
||||
infer.purgeMarkedVariables(file.scope);
|
||||
srv.signal("afterLoad", file);
|
||||
});
|
||||
return file;
|
||||
}
|
||||
|
||||
function ensureFile(srv, name, text) {
|
||||
var known = findFile(srv.files, name);
|
||||
if (known) {
|
||||
if (text) clearFile(srv, known, text);
|
||||
return;
|
||||
}
|
||||
|
||||
var file = new File(name);
|
||||
srv.files.push(file);
|
||||
if (text) {
|
||||
updateText(file, text, srv);
|
||||
} else if (srv.options.async) {
|
||||
srv.startAsyncAction();
|
||||
srv.options.getFile(name, function(err, text) {
|
||||
updateText(file, text || "", srv);
|
||||
srv.finishAsyncAction(err);
|
||||
});
|
||||
} else {
|
||||
updateText(file, srv.options.getFile(name) || "", srv);
|
||||
}
|
||||
}
|
||||
|
||||
function clearFile(srv, file, newText) {
|
||||
if (file.scope) {
|
||||
infer.withContext(srv.cx, function() {
|
||||
// FIXME try to batch purges into a single pass (each call needs
|
||||
// to traverse the whole graph)
|
||||
infer.purgeTypes(file.name);
|
||||
infer.markVariablesDefinedBy(file.scope, file.name);
|
||||
infer.purgeMarkedVariables(file.scope);
|
||||
});
|
||||
file.scope = null;
|
||||
}
|
||||
if (newText != null) updateText(file, newText, srv);
|
||||
}
|
||||
|
||||
function fetchAll(srv, c) {
|
||||
var done = true, returned = false;
|
||||
for (var i = 0; i < srv.files.length; ++i) {
|
||||
var file = srv.files[i];
|
||||
if (file.text != null) continue;
|
||||
if (srv.options.async) {
|
||||
done = false;
|
||||
srv.options.getFile(file.name, function(err, text) {
|
||||
if (err && !returned) { returned = true; return c(err); }
|
||||
updateText(file, text || "", srv);
|
||||
fetchAll(srv, c);
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
updateText(file, srv.options.getFile(file.name) || "", srv);
|
||||
} catch (e) { return c(e); }
|
||||
}
|
||||
}
|
||||
if (done) c();
|
||||
}
|
||||
|
||||
function waitOnFetch(srv, c) {
|
||||
var done = function() {
|
||||
srv.off("everythingFetched", done);
|
||||
clearTimeout(timeout);
|
||||
analyzeAll(srv, c);
|
||||
};
|
||||
srv.on("everythingFetched", done);
|
||||
var timeout = setTimeout(done, srv.options.fetchTimeout);
|
||||
}
|
||||
|
||||
function analyzeAll(srv, c) {
|
||||
if (srv.pending) return waitOnFetch(srv, c);
|
||||
|
||||
var e = srv.fetchError;
|
||||
if (e) { srv.fetchError = null; return c(e); }
|
||||
|
||||
var done = true;
|
||||
for (var i = 0; i < srv.files.length; ++i) {
|
||||
var file = srv.files[i];
|
||||
if (file.text == null) done = false;
|
||||
else if (file.scope == null) analyzeFile(srv, file);
|
||||
}
|
||||
if (done) c();
|
||||
else waitOnFetch(srv, c);
|
||||
}
|
||||
|
||||
function findFile(arr, name) {
|
||||
for (var i = 0; i < arr.length; ++i) {
|
||||
var file = arr[i];
|
||||
if (file.name == name && file.type != "part") return file;
|
||||
}
|
||||
}
|
||||
|
||||
function firstLine(str) {
|
||||
var end = str.indexOf("\n");
|
||||
if (end < 0) return str;
|
||||
return str.slice(0, end);
|
||||
}
|
||||
|
||||
function findMatchingPosition(line, file, near) {
|
||||
var pos = Math.max(0, near - 500), closest = null;
|
||||
if (!/^\s*$/.test(line)) for (;;) {
|
||||
var found = file.indexOf(line, pos);
|
||||
if (found < 0 || found > near + 500) break;
|
||||
if (closest == null || Math.abs(closest - near) > Math.abs(found - near))
|
||||
closest = found;
|
||||
pos = found + line.length;
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
function scopeDepth(s) {
|
||||
for (var i = 0; s; ++i, s = s.prev) {}
|
||||
return i;
|
||||
}
|
||||
|
||||
function ternError(msg) {
|
||||
var err = new Error(msg);
|
||||
err.name = "TernError";
|
||||
return err;
|
||||
}
|
||||
|
||||
function resolveFile(srv, localFiles, name) {
|
||||
var isRef = name.match(/^#(\d+)$/);
|
||||
if (!isRef) return findFile(srv.files, name);
|
||||
|
||||
var file = localFiles[isRef[1]];
|
||||
if (!file) throw ternError("Reference to unknown file " + name);
|
||||
if (file.type == "full") return findFile(srv.files, file.name);
|
||||
|
||||
// This is a partial file
|
||||
|
||||
var realFile = file.backing = findFile(srv.files, file.name);
|
||||
var offset = file.offset;
|
||||
if (file.offsetLines) offset = {line: file.offsetLines, ch: 0};
|
||||
file.offset = offset = resolvePos(realFile, file.offsetLines == null ? file.offset : {line: file.offsetLines, ch: 0}, true);
|
||||
var line = firstLine(file.text);
|
||||
var foundPos = findMatchingPosition(line, realFile.text, offset);
|
||||
var pos = foundPos == null ? Math.max(0, realFile.text.lastIndexOf("\n", offset)) : foundPos;
|
||||
|
||||
infer.withContext(srv.cx, function() {
|
||||
infer.purgeTypes(file.name, pos, pos + file.text.length);
|
||||
|
||||
var text = file.text, m;
|
||||
if (m = text.match(/(?:"([^"]*)"|([\w$]+))\s*:\s*function\b/)) {
|
||||
var objNode = walk.findNodeAround(file.backing.ast, pos, "ObjectExpression");
|
||||
if (objNode && objNode.node.objType)
|
||||
var inObject = {type: objNode.node.objType, prop: m[2] || m[1]};
|
||||
}
|
||||
if (foundPos && (m = line.match(/^(.*?)\bfunction\b/))) {
|
||||
var cut = m[1].length, white = "";
|
||||
for (var i = 0; i < cut; ++i) white += " ";
|
||||
text = white + text.slice(cut);
|
||||
var atFunction = true;
|
||||
}
|
||||
|
||||
var scopeStart = infer.scopeAt(realFile.ast, pos, realFile.scope);
|
||||
var scopeEnd = infer.scopeAt(realFile.ast, pos + text.length, realFile.scope);
|
||||
var scope = file.scope = scopeDepth(scopeStart) < scopeDepth(scopeEnd) ? scopeEnd : scopeStart;
|
||||
infer.markVariablesDefinedBy(scopeStart, file.name, pos, pos + file.text.length);
|
||||
file.ast = infer.parse(file.text, srv.passes, {directSourceFile: file});
|
||||
infer.analyze(file.ast, file.name, scope, srv.passes);
|
||||
infer.purgeMarkedVariables(scopeStart);
|
||||
|
||||
// This is a kludge to tie together the function types (if any)
|
||||
// outside and inside of the fragment, so that arguments and
|
||||
// return values have some information known about them.
|
||||
tieTogether: if (inObject || atFunction) {
|
||||
var newInner = infer.scopeAt(file.ast, line.length, scopeStart);
|
||||
if (!newInner.fnType) break tieTogether;
|
||||
if (inObject) {
|
||||
var prop = inObject.type.getProp(inObject.prop);
|
||||
prop.addType(newInner.fnType);
|
||||
} else if (atFunction) {
|
||||
var inner = infer.scopeAt(realFile.ast, pos + line.length, realFile.scope);
|
||||
if (inner == scopeStart || !inner.fnType) break tieTogether;
|
||||
var fOld = inner.fnType, fNew = newInner.fnType;
|
||||
if (!fNew || (fNew.name != fOld.name && fOld.name)) break tieTogether;
|
||||
for (var i = 0, e = Math.min(fOld.args.length, fNew.args.length); i < e; ++i)
|
||||
fOld.args[i].propagate(fNew.args[i]);
|
||||
fOld.self.propagate(fNew.self);
|
||||
fNew.retval.propagate(fOld.retval);
|
||||
}
|
||||
}
|
||||
});
|
||||
return file;
|
||||
}
|
||||
|
||||
function isPosition(val) {
|
||||
return typeof val == "number" || typeof val == "object" &&
|
||||
typeof val.line == "number" && typeof val.ch == "number";
|
||||
}
|
||||
|
||||
// Baseline query document validation
|
||||
function invalidDoc(doc) {
|
||||
if (doc.query) {
|
||||
if (typeof doc.query.type != "string") return ".query.type must be a string";
|
||||
if (doc.query.start && !isPosition(doc.query.start)) return ".query.start must be a position";
|
||||
if (doc.query.end && !isPosition(doc.query.end)) return ".query.end must be a position";
|
||||
}
|
||||
if (doc.files) {
|
||||
if (!Array.isArray(doc.files)) return "Files property must be an array";
|
||||
for (var i = 0; i < doc.files.length; ++i) {
|
||||
var file = doc.files[i];
|
||||
if (typeof file != "object") return ".files[n] must be objects";
|
||||
else if (typeof file.text != "string") return ".files[n].text must be a string";
|
||||
else if (typeof file.name != "string") return ".files[n].name must be a string";
|
||||
else if (file.type == "part") {
|
||||
if (!isPosition(file.offset) && typeof file.offsetLines != "number")
|
||||
return ".files[n].offset must be a position";
|
||||
} else if (file.type != "full") return ".files[n].type must be \"full\" or \"part\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var offsetSkipLines = 25;
|
||||
|
||||
function findLineStart(file, line) {
|
||||
var text = file.text, offsets = file.lineOffsets || (file.lineOffsets = [0]);
|
||||
var pos = 0, curLine = 0;
|
||||
var storePos = Math.min(Math.floor(line / offsetSkipLines), offsets.length - 1);
|
||||
var pos = offsets[storePos], curLine = storePos * offsetSkipLines;
|
||||
|
||||
while (curLine < line) {
|
||||
++curLine;
|
||||
pos = text.indexOf("\n", pos) + 1;
|
||||
if (pos == 0) return null;
|
||||
if (curLine % offsetSkipLines == 0) offsets.push(pos);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
function resolvePos(file, pos, tolerant) {
|
||||
if (typeof pos != "number") {
|
||||
var lineStart = findLineStart(file, pos.line);
|
||||
if (lineStart == null) {
|
||||
if (tolerant) pos = file.text.length;
|
||||
else throw ternError("File doesn't contain a line " + pos.line);
|
||||
} else {
|
||||
pos = lineStart + pos.ch;
|
||||
}
|
||||
}
|
||||
if (pos > file.text.length) {
|
||||
if (tolerant) pos = file.text.length;
|
||||
else throw ternError("Position " + pos + " is outside of file.");
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
function asLineChar(file, pos) {
|
||||
if (!file) return {line: 0, ch: 0};
|
||||
var offsets = file.lineOffsets || (file.lineOffsets = [0]);
|
||||
var text = file.text, line, lineStart;
|
||||
for (var i = offsets.length - 1; i >= 0; --i) if (offsets[i] <= pos) {
|
||||
line = i * offsetSkipLines;
|
||||
lineStart = offsets[i];
|
||||
}
|
||||
for (;;) {
|
||||
var eol = text.indexOf("\n", lineStart);
|
||||
if (eol >= pos || eol < 0) break;
|
||||
lineStart = eol + 1;
|
||||
++line;
|
||||
}
|
||||
return {line: line, ch: pos - lineStart};
|
||||
}
|
||||
|
||||
function outputPos(query, file, pos) {
|
||||
if (query.lineCharPositions) {
|
||||
var out = asLineChar(file, pos);
|
||||
if (file.type == "part")
|
||||
out.line += file.offsetLines != null ? file.offsetLines : asLineChar(file.backing, file.offset).line;
|
||||
return out;
|
||||
} else {
|
||||
return pos + (file.type == "part" ? file.offset : 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete empty fields from result objects
|
||||
function clean(obj) {
|
||||
for (var prop in obj) if (obj[prop] == null) delete obj[prop];
|
||||
return obj;
|
||||
}
|
||||
function maybeSet(obj, prop, val) {
|
||||
if (val != null) obj[prop] = val;
|
||||
}
|
||||
|
||||
// Built-in query types
|
||||
|
||||
function compareCompletions(a, b) {
|
||||
if (typeof a != "string") { a = a.name; b = b.name; }
|
||||
var aUp = /^[A-Z]/.test(a), bUp = /^[A-Z]/.test(b);
|
||||
if (aUp == bUp) return a < b ? -1 : a == b ? 0 : 1;
|
||||
else return aUp ? 1 : -1;
|
||||
}
|
||||
|
||||
function isStringAround(node, start, end) {
|
||||
return node.type == "Literal" && typeof node.value == "string" &&
|
||||
node.start == start - 1 && node.end <= end + 1;
|
||||
}
|
||||
|
||||
function findCompletions(srv, query, file) {
|
||||
if (query.end == null) throw ternError("missing .query.end field");
|
||||
var wordStart = resolvePos(file, query.end), wordEnd = wordStart, text = file.text;
|
||||
while (wordStart && acorn.isIdentifierChar(text.charCodeAt(wordStart - 1))) --wordStart;
|
||||
if (query.expandWordForward !== false)
|
||||
while (wordEnd < text.length && acorn.isIdentifierChar(text.charCodeAt(wordEnd))) ++wordEnd;
|
||||
var word = text.slice(wordStart, wordEnd), completions = [];
|
||||
if (query.caseInsensitive) word = word.toLowerCase();
|
||||
var wrapAsObjs = query.types || query.depths || query.docs || query.urls || query.origins;
|
||||
|
||||
function gather(prop, obj, depth) {
|
||||
// 'hasOwnProperty' and such are usually just noise, leave them
|
||||
// out when no prefix is provided.
|
||||
if (query.omitObjectPrototype !== false && obj == srv.cx.protos.Object && !word) return;
|
||||
if (query.filter !== false && word &&
|
||||
(query.caseInsensitive ? prop.toLowerCase() : prop).indexOf(word) != 0) return;
|
||||
for (var i = 0; i < completions.length; ++i) {
|
||||
var c = completions[i];
|
||||
if ((wrapAsObjs ? c.name : c) == prop) return;
|
||||
}
|
||||
var rec = wrapAsObjs ? {name: prop} : prop;
|
||||
completions.push(rec);
|
||||
|
||||
if (query.types || query.docs || query.urls || query.origins) {
|
||||
var val = obj ? obj.props[prop] : infer.ANull;
|
||||
infer.resetGuessing();
|
||||
var type = val.getType();
|
||||
rec.guess = infer.didGuess();
|
||||
if (query.types)
|
||||
rec.type = infer.toString(type);
|
||||
if (query.docs)
|
||||
maybeSet(rec, "doc", val.doc || type && type.doc);
|
||||
if (query.urls)
|
||||
maybeSet(rec, "url", val.url || type && type.url);
|
||||
if (query.origins)
|
||||
maybeSet(rec, "origin", val.origin || type && type.origin);
|
||||
}
|
||||
if (query.depths) rec.depth = depth;
|
||||
}
|
||||
|
||||
var memberExpr = infer.findExpressionAround(file.ast, null, wordStart, file.scope, "MemberExpression");
|
||||
if (memberExpr &&
|
||||
(memberExpr.node.computed ? isStringAround(memberExpr.node.property, wordStart, wordEnd)
|
||||
: memberExpr.node.object.end < wordStart)) {
|
||||
var prop = memberExpr.node.property;
|
||||
prop = prop.type == "Literal" ? prop.value.slice(1) : prop.name;
|
||||
|
||||
memberExpr.node = memberExpr.node.object;
|
||||
var tp = infer.expressionType(memberExpr);
|
||||
if (tp) infer.forAllPropertiesOf(tp, gather);
|
||||
|
||||
if (!completions.length && query.guess !== false && tp && tp.guessProperties) {
|
||||
tp.guessProperties(function(p, o, d) {if (p != prop && p != "✖") gather(p, o, d);});
|
||||
}
|
||||
if (!completions.length && word.length >= 2 && query.guess !== false)
|
||||
for (var prop in srv.cx.props) gather(prop, srv.cx.props[prop][0], 0);
|
||||
} else {
|
||||
infer.forAllLocalsAt(file.ast, wordStart, file.scope, gather);
|
||||
}
|
||||
|
||||
if (query.sort !== false) completions.sort(compareCompletions);
|
||||
|
||||
return {start: outputPos(query, file, wordStart),
|
||||
end: outputPos(query, file, wordEnd),
|
||||
completions: completions};
|
||||
}
|
||||
|
||||
function findProperties(srv, query) {
|
||||
var prefix = query.prefix, found = [];
|
||||
for (var prop in srv.cx.props)
|
||||
if (prop != "<i>" && (!prefix || prop.indexOf(prefix) == 0)) found.push(prop);
|
||||
if (query.sort !== false) found.sort(compareCompletions);
|
||||
return {completions: found};
|
||||
}
|
||||
|
||||
var findExpr = exports.findQueryExpr = function(file, query, wide) {
|
||||
if (query.end == null) throw ternError("missing .query.end field");
|
||||
|
||||
if (query.variable) {
|
||||
var scope = infer.scopeAt(file.ast, resolvePos(file, query.end), file.scope);
|
||||
return {node: {type: "Identifier", name: query.variable, start: query.end, end: query.end + 1},
|
||||
state: scope};
|
||||
} else {
|
||||
var start = query.start && resolvePos(file, query.start), end = resolvePos(file, query.end);
|
||||
var expr = infer.findExpressionAt(file.ast, start, end, file.scope);
|
||||
if (expr) return expr;
|
||||
expr = infer.findExpressionAround(file.ast, start, end, file.scope);
|
||||
if (expr && (wide || (start == null ? end : start) - expr.node.start < 20 || expr.node.end - end < 20))
|
||||
return expr;
|
||||
throw ternError("No expression at the given position.");
|
||||
}
|
||||
};
|
||||
|
||||
function findTypeAt(_srv, query, file) {
|
||||
var expr = findExpr(file, query);
|
||||
infer.resetGuessing();
|
||||
var type = infer.expressionType(expr);
|
||||
if (query.preferFunction)
|
||||
type = type.getFunctionType() || type.getType();
|
||||
else
|
||||
type = type.getType();
|
||||
|
||||
if (expr.node.type == "Identifier")
|
||||
var exprName = expr.node.name;
|
||||
else if (expr.node.type == "MemberExpression" && !expr.node.computed)
|
||||
var exprName = expr.node.property.name;
|
||||
|
||||
if (query.depth != null && typeof query.depth != "number")
|
||||
throw ternError(".query.depth must be a number");
|
||||
|
||||
var result = {guess: infer.didGuess(),
|
||||
type: infer.toString(type, query.depth),
|
||||
name: type && type.name,
|
||||
exprName: exprName};
|
||||
if (type) storeTypeDocs(type, result);
|
||||
|
||||
return clean(result);
|
||||
}
|
||||
|
||||
function findDocs(_srv, query, file) {
|
||||
var expr = findExpr(file, query);
|
||||
var type = infer.expressionType(expr);
|
||||
var result = {url: type.url, doc: type.doc};
|
||||
var inner = type.getType();
|
||||
if (inner) storeTypeDocs(inner, result);
|
||||
return clean(result);
|
||||
}
|
||||
|
||||
function storeTypeDocs(type, out) {
|
||||
if (!out.url) out.url = type.url;
|
||||
if (!out.doc) out.doc = type.doc;
|
||||
if (!out.origin) out.origin = type.origin;
|
||||
var ctor, boring = infer.cx().protos;
|
||||
if (!out.url && !out.doc && type.proto && (ctor = type.proto.hasCtor) &&
|
||||
type.proto != boring.Object && type.proto != boring.Function && type.proto != boring.Array) {
|
||||
out.url = ctor.url;
|
||||
out.doc = ctor.doc;
|
||||
}
|
||||
}
|
||||
|
||||
var getSpan = exports.getSpan = function(obj) {
|
||||
if (!obj.origin) return;
|
||||
if (obj.originNode) {
|
||||
var node = obj.originNode;
|
||||
if (/^Function/.test(node.type) && node.id) node = node.id;
|
||||
return {origin: obj.origin, node: node};
|
||||
}
|
||||
if (obj.span) return {origin: obj.origin, span: obj.span};
|
||||
};
|
||||
|
||||
var storeSpan = exports.storeSpan = function(srv, query, span, target) {
|
||||
target.origin = span.origin;
|
||||
if (span.span) {
|
||||
var m = /^(\d+)\[(\d+):(\d+)\]-(\d+)\[(\d+):(\d+)\]$/.exec(span.span);
|
||||
target.start = query.lineCharPositions ? {line: Number(m[2]), ch: Number(m[3])} : Number(m[1]);
|
||||
target.end = query.lineCharPositions ? {line: Number(m[5]), ch: Number(m[6])} : Number(m[4]);
|
||||
} else {
|
||||
var file = findFile(srv.files, span.origin);
|
||||
target.start = outputPos(query, file, span.node.start);
|
||||
target.end = outputPos(query, file, span.node.end);
|
||||
}
|
||||
};
|
||||
|
||||
function findDef(srv, query, file) {
|
||||
var expr = findExpr(file, query);
|
||||
infer.resetGuessing();
|
||||
var type = infer.expressionType(expr);
|
||||
if (infer.didGuess()) return {};
|
||||
|
||||
var span = getSpan(type);
|
||||
var result = {url: type.url, doc: type.doc, origin: type.origin};
|
||||
|
||||
if (type.types) for (var i = type.types.length - 1; i >= 0; --i) {
|
||||
var tp = type.types[i];
|
||||
storeTypeDocs(tp, result);
|
||||
if (!span) span = getSpan(tp);
|
||||
}
|
||||
|
||||
if (span && span.node) { // refers to a loaded file
|
||||
var spanFile = span.node.sourceFile || findFile(srv.files, span.origin);
|
||||
var start = outputPos(query, spanFile, span.node.start), end = outputPos(query, spanFile, span.node.end);
|
||||
result.start = start; result.end = end;
|
||||
result.file = span.origin;
|
||||
var cxStart = Math.max(0, span.node.start - 50);
|
||||
result.contextOffset = span.node.start - cxStart;
|
||||
result.context = spanFile.text.slice(cxStart, cxStart + 50);
|
||||
} else if (span) { // external
|
||||
result.file = span.origin;
|
||||
storeSpan(srv, query, span, result);
|
||||
}
|
||||
return clean(result);
|
||||
}
|
||||
|
||||
function findRefsToVariable(srv, query, file, expr, checkShadowing) {
|
||||
var name = expr.node.name;
|
||||
|
||||
for (var scope = expr.state; scope && !(name in scope.props); scope = scope.prev) {}
|
||||
if (!scope) throw ternError("Could not find a definition for " + name + " " + !!srv.cx.topScope.props.x);
|
||||
|
||||
var type, refs = [];
|
||||
function storeRef(file) {
|
||||
return function(node, scopeHere) {
|
||||
if (checkShadowing) for (var s = scopeHere; s != scope; s = s.prev) {
|
||||
var exists = s.hasProp(checkShadowing);
|
||||
if (exists)
|
||||
throw ternError("Renaming `" + name + "` to `" + checkShadowing + "` would make a variable at line " +
|
||||
(asLineChar(file, node.start).line + 1) + " point to the definition at line " +
|
||||
(asLineChar(file, exists.name.start).line + 1));
|
||||
}
|
||||
refs.push({file: file.name,
|
||||
start: outputPos(query, file, node.start),
|
||||
end: outputPos(query, file, node.end)});
|
||||
};
|
||||
}
|
||||
|
||||
if (scope.node) {
|
||||
type = "local";
|
||||
if (checkShadowing) {
|
||||
for (var prev = scope.prev; prev; prev = prev.prev)
|
||||
if (checkShadowing in prev.props) break;
|
||||
if (prev) infer.findRefs(scope.node, scope, checkShadowing, prev, function(node) {
|
||||
throw ternError("Renaming `" + name + "` to `" + checkShadowing + "` would shadow the definition used at line " +
|
||||
(asLineChar(file, node.start).line + 1));
|
||||
});
|
||||
}
|
||||
infer.findRefs(scope.node, scope, name, scope, storeRef(file));
|
||||
} else {
|
||||
type = "global";
|
||||
for (var i = 0; i < srv.files.length; ++i) {
|
||||
var cur = srv.files[i];
|
||||
infer.findRefs(cur.ast, cur.scope, name, scope, storeRef(cur));
|
||||
}
|
||||
}
|
||||
|
||||
return {refs: refs, type: type, name: name};
|
||||
}
|
||||
|
||||
function findRefsToProperty(srv, query, expr, prop) {
|
||||
var objType = infer.expressionType(expr).getType();
|
||||
if (!objType) throw ternError("Couldn't determine type of base object.");
|
||||
|
||||
var refs = [];
|
||||
function storeRef(file) {
|
||||
return function(node) {
|
||||
refs.push({file: file.name,
|
||||
start: outputPos(query, file, node.start),
|
||||
end: outputPos(query, file, node.end)});
|
||||
};
|
||||
}
|
||||
for (var i = 0; i < srv.files.length; ++i) {
|
||||
var cur = srv.files[i];
|
||||
infer.findPropRefs(cur.ast, cur.scope, objType, prop.name, storeRef(cur));
|
||||
}
|
||||
|
||||
return {refs: refs, name: prop.name};
|
||||
}
|
||||
|
||||
function findRefs(srv, query, file) {
|
||||
var expr = findExpr(file, query, true);
|
||||
if (expr && expr.node.type == "Identifier") {
|
||||
return findRefsToVariable(srv, query, file, expr);
|
||||
} else if (expr && expr.node.type == "MemberExpression" && !expr.node.computed) {
|
||||
var p = expr.node.property;
|
||||
expr.node = expr.node.object;
|
||||
return findRefsToProperty(srv, query, expr, p);
|
||||
} else if (expr && expr.node.type == "ObjectExpression") {
|
||||
var pos = resolvePos(file, query.end);
|
||||
for (var i = 0; i < expr.node.properties.length; ++i) {
|
||||
var k = expr.node.properties[i].key;
|
||||
if (k.start <= pos && k.end >= pos)
|
||||
return findRefsToProperty(srv, query, expr, k);
|
||||
}
|
||||
}
|
||||
throw ternError("Not at a variable or property name.");
|
||||
}
|
||||
|
||||
function buildRename(srv, query, file) {
|
||||
if (typeof query.newName != "string") throw ternError(".query.newName should be a string");
|
||||
var expr = findExpr(file, query);
|
||||
if (!expr || expr.node.type != "Identifier") throw ternError("Not at a variable.");
|
||||
|
||||
var data = findRefsToVariable(srv, query, file, expr, query.newName), refs = data.refs;
|
||||
delete data.refs;
|
||||
data.files = srv.files.map(function(f){return f.name;});
|
||||
|
||||
var changes = data.changes = [];
|
||||
for (var i = 0; i < refs.length; ++i) {
|
||||
var use = refs[i];
|
||||
use.text = query.newName;
|
||||
changes.push(use);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function listFiles(srv) {
|
||||
return {files: srv.files.map(function(f){return f.name;})};
|
||||
}
|
||||
|
||||
exports.version = "0.5.1";
|
||||
});
|
|
@ -0,0 +1,4 @@
|
|||
"use strict";
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const { require } = devtools;
|
|
@ -0,0 +1,26 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that tern autocompletions work.
|
||||
*/
|
||||
|
||||
const tern = require("tern/tern");
|
||||
const ecma5 = require("tern/ecma5");
|
||||
|
||||
function run_test() {
|
||||
do_test_pending();
|
||||
|
||||
const server = new tern.Server({ defs: [ecma5] });
|
||||
const code = "[].";
|
||||
const query = { type: "completions", file: "test", end: code.length };
|
||||
const files = [{ type: "full", name: "test", text: code }];
|
||||
|
||||
server.request({ query: query, files: files }, (error, response) => {
|
||||
do_check_eq(error, null);
|
||||
do_check_true(!!response);
|
||||
do_check_true(Array.isArray(response.completions));
|
||||
do_check_true(response.completions.indexOf("concat") != -1);
|
||||
do_test_finished();
|
||||
});
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that we can require tern.
|
||||
*/
|
||||
|
||||
function run_test() {
|
||||
const tern = require("tern/tern");
|
||||
const ecma5 = require("tern/ecma5");
|
||||
const browser = require("tern/browser");
|
||||
do_check_true(!!tern);
|
||||
do_check_true(!!ecma5);
|
||||
do_check_true(!!browser);
|
||||
do_check_eq(typeof tern.Server, "function");
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
[DEFAULT]
|
||||
head = head_tern.js
|
||||
tail =
|
||||
|
||||
[test_autocompletion.js]
|
||||
[test_import_tern.js]
|
|
@ -893,14 +893,9 @@ FileAppender.prototype = {
|
|||
_getFile: function() {
|
||||
if (!this._fileReadyPromise) {
|
||||
this._fileReadyPromise = this._openFile();
|
||||
return this._fileReadyPromise;
|
||||
}
|
||||
|
||||
return this._fileReadyPromise.then(_ => {
|
||||
if (!this._file) {
|
||||
return this._openFile();
|
||||
}
|
||||
});
|
||||
return this._fileReadyPromise;
|
||||
},
|
||||
|
||||
doAppend: function (formatted) {
|
||||
|
|
|
@ -288,6 +288,11 @@ add_task(function test_FileAppender() {
|
|||
|
||||
add_task(function test_BoundedFileAppender() {
|
||||
let dir = OS.Path.join(do_get_profile().path, "test_Log");
|
||||
|
||||
if (!(yield OS.File.exists(dir))) {
|
||||
yield OS.File.makeDir(dir);
|
||||
}
|
||||
|
||||
let path = OS.Path.join(dir, "test_BoundedFileAppender");
|
||||
// This appender will hold about two lines at a time.
|
||||
let appender = new Log.BoundedFileAppender(path, testFormatter, 40);
|
||||
|
|
Загрузка…
Ссылка в новой задаче