mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge fx-team to m-c.
This commit is contained in:
commit
8a68d16b9a
@ -1094,12 +1094,18 @@ pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","t
|
||||
pref("devtools.toolbox.sideEnabled", true);
|
||||
pref("devtools.toolbox.zoomValue", "1");
|
||||
|
||||
// Inspector preferences
|
||||
// Enable the Inspector
|
||||
pref("devtools.inspector.enabled", true);
|
||||
// What was the last active sidebar in the inspector
|
||||
pref("devtools.inspector.activeSidebar", "ruleview");
|
||||
// Enable the markup preview
|
||||
pref("devtools.inspector.markupPreview", false);
|
||||
pref("devtools.inspector.remote", false);
|
||||
// Expand pseudo-elements by default in the rule-view
|
||||
pref("devtools.inspector.show_pseudo_elements", true);
|
||||
// The default size for image preview tooltips in the rule-view/computed-view/markup-view
|
||||
pref("devtools.inspector.imagePreviewTooltipSize", 300);
|
||||
|
||||
// DevTools default color unit
|
||||
pref("devtools.defaultColorUnit", "hex");
|
||||
|
@ -142,18 +142,23 @@ let wrapper = {
|
||||
// Remember who it was so we can log out next time.
|
||||
setPreviousAccountNameHash(newAccountEmail);
|
||||
|
||||
fxAccounts.setSignedInUser(accountData).then(
|
||||
() => {
|
||||
this.injectData("message", { status: "login" });
|
||||
// until we sort out a better UX, just leave the jelly page in place.
|
||||
// If the account email is not yet verified, it will tell the user to
|
||||
// go check their email, but then it will *not* change state after
|
||||
// the verification completes (the browser will begin syncing, but
|
||||
// won't notify the user). If the email has already been verified,
|
||||
// the jelly will say "Welcome! You are successfully signed in as
|
||||
// EMAIL", but it won't then say "syncing started".
|
||||
},
|
||||
(err) => this.injectData("message", { status: "error", error: err })
|
||||
// A sync-specific hack - we want to ensure sync has been initialized
|
||||
// before we set the signed-in user.
|
||||
let xps = Cc["@mozilla.org/weave/service;1"]
|
||||
.getService(Ci.nsISupports)
|
||||
.wrappedJSObject;
|
||||
xps.whenLoaded().then(() => {
|
||||
return fxAccounts.setSignedInUser(accountData);
|
||||
}).then(() => {
|
||||
this.injectData("message", { status: "login" });
|
||||
// until we sort out a better UX, just leave the jelly page in place.
|
||||
// If the account email is not yet verified, it will tell the user to
|
||||
// go check their email, but then it will *not* change state after
|
||||
// the verification completes (the browser will begin syncing, but
|
||||
// won't notify the user). If the email has already been verified,
|
||||
// the jelly will say "Welcome! You are successfully signed in as
|
||||
// EMAIL", but it won't then say "syncing started".
|
||||
}, (err) => this.injectData("message", { status: "error", error: err })
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -152,8 +152,12 @@
|
||||
<panelview id="PanelUI-characterEncodingView" flex="1">
|
||||
<label value="&charsetMenu.label;" class="panel-subview-header"/>
|
||||
|
||||
<vbox id="PanelUI-characterEncodingView-customlist"
|
||||
<vbox id="PanelUI-characterEncodingView-pinned"
|
||||
class="PanelUI-characterEncodingView-list"/>
|
||||
<toolbarseparator/>
|
||||
<vbox id="PanelUI-characterEncodingView-charsets"
|
||||
class="PanelUI-characterEncodingView-list"/>
|
||||
<toolbarseparator/>
|
||||
<vbox>
|
||||
<label id="PanelUI-characterEncodingView-autodetect-label"/>
|
||||
<vbox id="PanelUI-characterEncodingView-autodetect"
|
||||
|
@ -18,6 +18,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentlyClosedTabsAndWindowsMenuUtils",
|
||||
"resource:///modules/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
|
||||
"resource://gre/modules/ShortcutUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
|
||||
"resource://gre/modules/CharsetMenu.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "CharsetManager",
|
||||
"@mozilla.org/charset-converter-manager;1",
|
||||
"nsICharsetConverterManager");
|
||||
@ -650,131 +652,86 @@ const CustomizableWidgets = [{
|
||||
window.gBrowser.docShell &&
|
||||
window.gBrowser.docShell.mayEnableCharacterEncodingMenu);
|
||||
},
|
||||
getCharsetList: function(aSection, aDocument) {
|
||||
let currCharset = aDocument.defaultView.content.document.characterSet;
|
||||
|
||||
let list = "";
|
||||
try {
|
||||
let pref = "intl.charsetmenu.browser." + aSection;
|
||||
list = Services.prefs.getComplexValue(pref,
|
||||
Ci.nsIPrefLocalizedString).data;
|
||||
} catch (e) {}
|
||||
|
||||
list = list.trim();
|
||||
if (!list)
|
||||
return [];
|
||||
|
||||
list = list.split(",");
|
||||
|
||||
let items = [];
|
||||
for (let charset of list) {
|
||||
charset = charset.trim();
|
||||
|
||||
let notForBrowser = false;
|
||||
try {
|
||||
notForBrowser = CharsetManager.getCharsetData(charset,
|
||||
"notForBrowser");
|
||||
} catch (e) {}
|
||||
|
||||
if (notForBrowser)
|
||||
continue;
|
||||
|
||||
let title = charset;
|
||||
try {
|
||||
title = CharsetManager.getCharsetTitle(charset);
|
||||
} catch (e) {}
|
||||
|
||||
items.push({value: charset, name: title, current: charset == currCharset});
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
getAutoDetectors: function(aDocument) {
|
||||
let detectorEnum = CharsetManager.GetCharsetDetectorList();
|
||||
let currDetector;
|
||||
try {
|
||||
currDetector = Services.prefs.getComplexValue(
|
||||
"intl.charset.detector", Ci.nsIPrefLocalizedString).data;
|
||||
} catch (e) {}
|
||||
if (!currDetector)
|
||||
currDetector = "off";
|
||||
currDetector = "chardet." + currDetector;
|
||||
|
||||
let items = [];
|
||||
|
||||
while (detectorEnum.hasMore()) {
|
||||
let detector = detectorEnum.getNext();
|
||||
|
||||
let title = detector;
|
||||
try {
|
||||
title = CharsetManager.getCharsetTitle(detector);
|
||||
} catch (e) {}
|
||||
|
||||
items.push({value: detector, name: title, current: detector == currDetector});
|
||||
}
|
||||
|
||||
items.sort((aItem1, aItem2) => {
|
||||
return aItem1.name.localeCompare(aItem2.name);
|
||||
});
|
||||
|
||||
return items;
|
||||
},
|
||||
populateList: function(aDocument, aContainerId, aSection) {
|
||||
let containerElem = aDocument.getElementById(aContainerId);
|
||||
|
||||
while (containerElem.firstChild) {
|
||||
containerElem.removeChild(containerElem.firstChild);
|
||||
}
|
||||
|
||||
containerElem.addEventListener("command", this.onCommand, false);
|
||||
|
||||
let list = [];
|
||||
if (aSection == "autodetect") {
|
||||
list = this.getAutoDetectors(aDocument);
|
||||
} else if (aSection == "browser") {
|
||||
let staticList = this.getCharsetList("static", aDocument);
|
||||
let cacheList = this.getCharsetList("cache", aDocument);
|
||||
// Combine lists, and de-duplicate.
|
||||
let checkedIn = new Set();
|
||||
for (let item of staticList.concat(cacheList)) {
|
||||
let itemName = item.name.toLowerCase();
|
||||
if (!checkedIn.has(itemName)) {
|
||||
list.push(item);
|
||||
checkedIn.add(itemName);
|
||||
}
|
||||
}
|
||||
}
|
||||
let list = this.charsetInfo[aSection];
|
||||
|
||||
// Update the appearance of the buttons when it's not possible to
|
||||
// customize encoding.
|
||||
let disabled = this.maybeDisableMenu(aDocument);
|
||||
for (let item of list) {
|
||||
let elem = aDocument.createElementNS(kNSXUL, "toolbarbutton");
|
||||
elem.setAttribute("label", item.name);
|
||||
elem.section = aSection;
|
||||
elem.value = item.value;
|
||||
if (item.current)
|
||||
elem.setAttribute("current", "true");
|
||||
if (disabled)
|
||||
elem.setAttribute("disabled", "true");
|
||||
elem.setAttribute("label", item.label);
|
||||
elem.section = aSection == "detectors" ? "detectors" : "charsets";
|
||||
elem.value = item.id;
|
||||
elem.setAttribute("class", "subviewbutton");
|
||||
containerElem.appendChild(elem);
|
||||
}
|
||||
},
|
||||
updateCurrentCharset: function(aDocument) {
|
||||
let content = aDocument.defaultView.content;
|
||||
let currentCharset = content && content.document && content.document.characterSet;
|
||||
if (currentCharset) {
|
||||
currentCharset = aDocument.defaultView.FoldCharset(currentCharset);
|
||||
}
|
||||
currentCharset = currentCharset ? ("charset." + currentCharset) : "";
|
||||
|
||||
let pinnedContainer = aDocument.getElementById("PanelUI-characterEncodingView-pinned");
|
||||
let charsetContainer = aDocument.getElementById("PanelUI-characterEncodingView-charsets");
|
||||
let elements = [...(pinnedContainer.childNodes), ...(charsetContainer.childNodes)];
|
||||
|
||||
this._updateElements(elements, currentCharset);
|
||||
},
|
||||
updateCurrentDetector: function(aDocument) {
|
||||
let detectorContainer = aDocument.getElementById("PanelUI-characterEncodingView-autodetect");
|
||||
let detectorEnum = CharsetManager.GetCharsetDetectorList();
|
||||
let currentDetector;
|
||||
try {
|
||||
currentDetector = Services.prefs.getComplexValue(
|
||||
"intl.charset.detector", Ci.nsIPrefLocalizedString).data;
|
||||
} catch (e) {}
|
||||
currentDetector = "chardet." + (currentDetector || "off");
|
||||
|
||||
this._updateElements(detectorContainer.childNodes, currentDetector);
|
||||
},
|
||||
_updateElements: function(aElements, aCurrentItem) {
|
||||
if (!aElements.length) {
|
||||
return;
|
||||
}
|
||||
let disabled = this.maybeDisableMenu(aElements[0].ownerDocument);
|
||||
for (let elem of aElements) {
|
||||
if (disabled) {
|
||||
elem.setAttribute("disabled", "true");
|
||||
} else {
|
||||
elem.removeAttribute("disabled");
|
||||
}
|
||||
if (elem.value.toLowerCase() == aCurrentItem.toLowerCase()) {
|
||||
elem.setAttribute("current", "true");
|
||||
} else {
|
||||
elem.removeAttribute("current");
|
||||
}
|
||||
}
|
||||
},
|
||||
onViewShowing: function(aEvent) {
|
||||
let document = aEvent.target.ownerDocument;
|
||||
|
||||
let autoDetectLabelId = "PanelUI-characterEncodingView-autodetect-label";
|
||||
let autoDetectLabel = document.getElementById(autoDetectLabelId);
|
||||
let label = CharsetBundle.GetStringFromName("charsetMenuAutodet");
|
||||
autoDetectLabel.setAttribute("value", label);
|
||||
|
||||
this.populateList(document,
|
||||
"PanelUI-characterEncodingView-customlist",
|
||||
"browser");
|
||||
this.populateList(document,
|
||||
"PanelUI-characterEncodingView-autodetect",
|
||||
"autodetect");
|
||||
if (!autoDetectLabel.hasAttribute("value")) {
|
||||
let label = CharsetBundle.GetStringFromName("charsetMenuAutodet");
|
||||
autoDetectLabel.setAttribute("value", label);
|
||||
this.populateList(document,
|
||||
"PanelUI-characterEncodingView-pinned",
|
||||
"pinnedCharsets");
|
||||
this.populateList(document,
|
||||
"PanelUI-characterEncodingView-charsets",
|
||||
"otherCharsets");
|
||||
this.populateList(document,
|
||||
"PanelUI-characterEncodingView-autodetect",
|
||||
"detectors");
|
||||
}
|
||||
this.updateCurrentDetector(document);
|
||||
this.updateCurrentCharset(document);
|
||||
},
|
||||
onCommand: function(aEvent) {
|
||||
let node = aEvent.target;
|
||||
@ -782,16 +739,16 @@ const CustomizableWidgets = [{
|
||||
return;
|
||||
}
|
||||
|
||||
CustomizableUI.hidePanelForNode(node);
|
||||
let window = node.ownerDocument.defaultView;
|
||||
let section = node.section;
|
||||
let value = node.value;
|
||||
|
||||
// The behavior as implemented here is directly based off of the
|
||||
// `MultiplexHandler()` method in browser.js.
|
||||
if (section == "browser") {
|
||||
window.BrowserSetForcedCharacterSet(value);
|
||||
} else if (section == "autodetect") {
|
||||
if (section != "detectors") {
|
||||
let charset = value.substring(value.indexOf('charset.') + 'charset.'.length);
|
||||
window.BrowserSetForcedCharacterSet(charset);
|
||||
} else {
|
||||
value = value.replace(/^chardet\./, "");
|
||||
if (value == "off") {
|
||||
value = "";
|
||||
@ -852,6 +809,9 @@ const CustomizableWidgets = [{
|
||||
}
|
||||
};
|
||||
CustomizableUI.addListener(listener);
|
||||
if (!this.charsetInfo) {
|
||||
this.charsetInfo = CharsetMenu.getData();
|
||||
}
|
||||
}
|
||||
}, {
|
||||
id: "email-link-button",
|
||||
|
@ -1060,17 +1060,25 @@ Toolbox.prototype = {
|
||||
let deferred = promise.defer();
|
||||
|
||||
if (this._inspector) {
|
||||
this._selection.destroy();
|
||||
this._selection = null;
|
||||
this._walker.release().then(
|
||||
// Selection is not always available.
|
||||
if (this._selection) {
|
||||
this._selection.destroy();
|
||||
this._selection = null;
|
||||
}
|
||||
|
||||
let walker = this._walker ? this._walker.release() : promise.resolve(null);
|
||||
walker.then(
|
||||
() => {
|
||||
this._inspector.destroy();
|
||||
this._highlighter.destroy();
|
||||
if (this._highlighter) {
|
||||
this._highlighter.destroy();
|
||||
}
|
||||
},
|
||||
(e) => {
|
||||
console.error("Walker.release() failed: " + e);
|
||||
this._inspector.destroy();
|
||||
return this._highlighter.destroy();
|
||||
|
||||
return this._highlighter ? this._highlighter.destroy() : promise.resolve(null);
|
||||
}
|
||||
).then(() => {
|
||||
this._inspector = null;
|
||||
|
@ -101,6 +101,10 @@ InspectorPanel.prototype = {
|
||||
return this._target.client.traits.editOuterHTML;
|
||||
},
|
||||
|
||||
get hasUrlToImageDataResolver() {
|
||||
return this._target.client.traits.urlToImageDataResolver;
|
||||
},
|
||||
|
||||
_deferredOpen: function(defaultSelection) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
|
@ -14,7 +14,6 @@ const COLLAPSE_ATTRIBUTE_LENGTH = 120;
|
||||
const COLLAPSE_DATA_URL_REGEX = /^data.+base64/;
|
||||
const COLLAPSE_DATA_URL_LENGTH = 60;
|
||||
const CONTAINER_FLASHING_DURATION = 500;
|
||||
const IMAGE_PREVIEW_MAX_DIM = 400;
|
||||
const NEW_SELECTION_HIGHLIGHTER_TIMER = 1000;
|
||||
|
||||
const {UndoStack} = require("devtools/shared/undo");
|
||||
@ -1270,16 +1269,17 @@ MarkupContainer.prototype = {
|
||||
data: def.promise
|
||||
};
|
||||
|
||||
this.node.getImageData(IMAGE_PREVIEW_MAX_DIM).then(data => {
|
||||
if (data) {
|
||||
data.data.string().then(str => {
|
||||
let res = {data: str, size: data.size};
|
||||
// Resolving the data promise and, to always keep tooltipData.data
|
||||
// as a promise, create a new one that resolves immediately
|
||||
def.resolve(res);
|
||||
this.tooltipData.data = promise.resolve(res);
|
||||
});
|
||||
}
|
||||
let maxDim = Services.prefs.getIntPref("devtools.inspector.imagePreviewTooltipSize");
|
||||
this.node.getImageData(maxDim).then(data => {
|
||||
data.data.string().then(str => {
|
||||
let res = {data: str, size: data.size};
|
||||
// Resolving the data promise and, to always keep tooltipData.data
|
||||
// as a promise, create a new one that resolves immediately
|
||||
def.resolve(res);
|
||||
this.tooltipData.data = promise.resolve(res);
|
||||
});
|
||||
}, () => {
|
||||
this.tooltipData.data = promise.reject();
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -1288,11 +1288,9 @@ MarkupContainer.prototype = {
|
||||
// We need to send again a request to gettooltipData even if one was sent for
|
||||
// the tooltip, because we want the full-size image
|
||||
this.node.getImageData().then(data => {
|
||||
if (data) {
|
||||
data.data.string().then(str => {
|
||||
clipboardHelper.copyString(str, this.markup.doc);
|
||||
});
|
||||
}
|
||||
data.data.string().then(str => {
|
||||
clipboardHelper.copyString(str, this.markup.doc);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@ -1300,6 +1298,8 @@ MarkupContainer.prototype = {
|
||||
if (this.tooltipData && target === this.tooltipData.target) {
|
||||
this.tooltipData.data.then(({data, size}) => {
|
||||
tooltip.setImageContent(data, size);
|
||||
}, () => {
|
||||
tooltip.setBrokenImageContent();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
@ -846,10 +846,10 @@ var Scratchpad = {
|
||||
|
||||
// Assemble the best possible stack we can given the properties we have.
|
||||
let stack;
|
||||
if (typeof error.stack == "string") {
|
||||
if (typeof error.stack == "string" && error.stack) {
|
||||
stack = error.stack;
|
||||
}
|
||||
else if (typeof error.fileName == "number") {
|
||||
else if (typeof error.fileName == "string") {
|
||||
stack = "@" + error.fileName;
|
||||
if (typeof error.lineNumber == "number") {
|
||||
stack += ":" + error.lineNumber;
|
||||
|
@ -13,7 +13,7 @@ function test()
|
||||
openScratchpad(runTests, {"state":{"text":""}});
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html,<p>test that exceptions our output as " +
|
||||
content.location = "data:text/html,<p>test that exceptions are output as " +
|
||||
"comments for 'display' and not sent to the console in Scratchpad";
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ function runTests()
|
||||
let openComment = "\n/*\n";
|
||||
let closeComment = "\n*/";
|
||||
let error = "throw new Error(\"Ouch!\")";
|
||||
let syntaxError = "(";
|
||||
|
||||
let tests = [{
|
||||
method: "display",
|
||||
@ -39,6 +40,13 @@ function runTests()
|
||||
scratchpad.uniqueName + ":1" + closeComment,
|
||||
label: "error display output",
|
||||
},
|
||||
{
|
||||
method: "display",
|
||||
code: syntaxError,
|
||||
result: syntaxError + openComment + "Exception: syntax error\n@" +
|
||||
scratchpad.uniqueName + ":1" + closeComment,
|
||||
label: "syntaxError display output",
|
||||
},
|
||||
{
|
||||
method: "run",
|
||||
code: message,
|
||||
@ -51,6 +59,13 @@ function runTests()
|
||||
result: error + openComment + "Exception: Ouch!\n@" +
|
||||
scratchpad.uniqueName + ":1" + closeComment,
|
||||
label: "error run output",
|
||||
},
|
||||
{
|
||||
method: "run",
|
||||
code: syntaxError,
|
||||
result: syntaxError + openComment + "Exception: syntax error\n@" +
|
||||
scratchpad.uniqueName + ":1" + closeComment,
|
||||
label: "syntaxError run output",
|
||||
}];
|
||||
|
||||
runAsyncTests(scratchpad, tests).then(finish);
|
||||
|
@ -29,7 +29,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "VariablesViewController",
|
||||
const GRADIENT_RE = /\b(repeating-)?(linear|radial)-gradient\(((rgb|hsl)a?\(.+?\)|[^\)])+\)/gi;
|
||||
const BORDERCOLOR_RE = /^border-[-a-z]*color$/ig;
|
||||
const BORDER_RE = /^border(-(top|bottom|left|right))?$/ig;
|
||||
const BACKGROUND_IMAGE_RE = /url\([\'\"]?(.*?)[\'\"]?\)/;
|
||||
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const SPECTRUM_FRAME = "chrome://browser/content/devtools/spectrum-frame.xhtml";
|
||||
const ESCAPE_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE;
|
||||
@ -569,9 +568,45 @@ Tooltip.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Fill the tooltip with an image, displayed over a tiled background useful
|
||||
* for transparent images. Also adds the image dimension as a label at the
|
||||
* bottom.
|
||||
* Uses the provided inspectorFront's getImageDataFromURL method to resolve
|
||||
* the relative URL on the server-side, in the page context, and then sets the
|
||||
* tooltip content with the resulting image just like |setImageContent| does.
|
||||
*
|
||||
* @return a promise that resolves when the image is shown in the tooltip
|
||||
*/
|
||||
setRelativeImageContent: function(imageUrl, inspectorFront, maxDim) {
|
||||
if (imageUrl.startsWith("data:")) {
|
||||
// If the imageUrl already is a data-url, save ourselves a round-trip
|
||||
this.setImageContent(imageUrl, {maxDim: maxDim});
|
||||
return promise.resolve();
|
||||
} else if (inspectorFront) {
|
||||
return inspectorFront.getImageDataFromURL(imageUrl, maxDim).then(res => {
|
||||
res.size.maxDim = maxDim;
|
||||
return res.data.string().then(str => {
|
||||
this.setImageContent(str, res.size);
|
||||
});
|
||||
}, () => {
|
||||
this.setBrokenImageContent();
|
||||
});
|
||||
}
|
||||
return promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Fill the tooltip with a message explaining the the image is missing
|
||||
*/
|
||||
setBrokenImageContent: function() {
|
||||
this.setTextContent({
|
||||
messages: [l10n.strings.GetStringFromName("previewTooltip.image.brokenImage")]
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fill the tooltip with an image and add the image dimension at the bottom.
|
||||
*
|
||||
* Only use this for absolute URLs that can be queried from the devtools
|
||||
* client-side. For relative URLs, use |setRelativeImageContent|.
|
||||
*
|
||||
* @param {string} imageUrl
|
||||
* The url to load the image from
|
||||
* @param {Object} options
|
||||
@ -585,57 +620,46 @@ Tooltip.prototype = {
|
||||
* a number here
|
||||
*/
|
||||
setImageContent: function(imageUrl, options={}) {
|
||||
// Main container
|
||||
let vbox = this.doc.createElement("vbox");
|
||||
vbox.setAttribute("align", "center");
|
||||
if (imageUrl) {
|
||||
// Main container
|
||||
let vbox = this.doc.createElement("vbox");
|
||||
vbox.setAttribute("align", "center");
|
||||
|
||||
// Display the image
|
||||
let image = this.doc.createElement("image");
|
||||
image.setAttribute("src", imageUrl);
|
||||
if (options.maxDim) {
|
||||
image.style.maxWidth = options.maxDim + "px";
|
||||
image.style.maxHeight = options.maxDim + "px";
|
||||
}
|
||||
vbox.appendChild(image);
|
||||
|
||||
// Dimension label
|
||||
let label = this.doc.createElement("label");
|
||||
label.classList.add("devtools-tooltip-caption");
|
||||
label.classList.add("theme-comment");
|
||||
if (options.naturalWidth && options.naturalHeight) {
|
||||
label.textContent = this._getImageDimensionLabel(options.naturalWidth,
|
||||
options.naturalHeight);
|
||||
} else {
|
||||
// If no dimensions were provided, load the image to get them
|
||||
label.textContent = l10n.strings.GetStringFromName("previewTooltip.image.brokenImage");
|
||||
let imgObj = new this.doc.defaultView.Image();
|
||||
imgObj.src = imageUrl;
|
||||
imgObj.onload = () => {
|
||||
imgObj.onload = null;
|
||||
label.textContent = this._getImageDimensionLabel(imgObj.naturalWidth,
|
||||
imgObj.naturalHeight);
|
||||
// Display the image
|
||||
let image = this.doc.createElement("image");
|
||||
image.setAttribute("src", imageUrl);
|
||||
if (options.maxDim) {
|
||||
image.style.maxWidth = options.maxDim + "px";
|
||||
image.style.maxHeight = options.maxDim + "px";
|
||||
}
|
||||
}
|
||||
vbox.appendChild(label);
|
||||
vbox.appendChild(image);
|
||||
|
||||
this.content = vbox;
|
||||
// Dimension label
|
||||
let label = this.doc.createElement("label");
|
||||
label.classList.add("devtools-tooltip-caption");
|
||||
label.classList.add("theme-comment");
|
||||
if (options.naturalWidth && options.naturalHeight) {
|
||||
label.textContent = this._getImageDimensionLabel(options.naturalWidth,
|
||||
options.naturalHeight);
|
||||
} else {
|
||||
// If no dimensions were provided, load the image to get them
|
||||
label.textContent = l10n.strings.GetStringFromName("previewTooltip.image.brokenImage");
|
||||
let imgObj = new this.doc.defaultView.Image();
|
||||
imgObj.src = imageUrl;
|
||||
imgObj.onload = () => {
|
||||
imgObj.onload = null;
|
||||
label.textContent = this._getImageDimensionLabel(imgObj.naturalWidth,
|
||||
imgObj.naturalHeight);
|
||||
}
|
||||
}
|
||||
vbox.appendChild(label);
|
||||
|
||||
this.content = vbox;
|
||||
}
|
||||
},
|
||||
|
||||
_getImageDimensionLabel: (w, h) => w + " x " + h,
|
||||
|
||||
/**
|
||||
* Exactly the same as the `image` function but takes a css background image
|
||||
* value instead : url(....)
|
||||
*/
|
||||
setCssBackgroundImageContent: function(cssBackground, sheetHref, maxDim=400) {
|
||||
let uri = getBackgroundImageUri(cssBackground, sheetHref);
|
||||
if (uri) {
|
||||
this.setImageContent(uri, {
|
||||
maxDim: maxDim
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fill the tooltip with a new instance of the spectrum color picker widget
|
||||
* initialized with the given color, and return a promise that resolves to
|
||||
@ -941,24 +965,6 @@ function isColorOnly(property, value) {
|
||||
property.match(BORDERCOLOR_RE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal util, returns the background image uri if any
|
||||
*/
|
||||
function getBackgroundImageUri(value, sheetHref) {
|
||||
let uriMatch = BACKGROUND_IMAGE_RE.exec(value);
|
||||
let uri = null;
|
||||
|
||||
if (uriMatch && uriMatch[1]) {
|
||||
uri = uriMatch[1];
|
||||
if (sheetHref) {
|
||||
let sheetUri = IOService.newURI(sheetHref, null, null);
|
||||
uri = sheetUri.resolve(uri);
|
||||
}
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* L10N utility class
|
||||
*/
|
||||
|
@ -6,28 +6,25 @@
|
||||
|
||||
const {Cc, Ci, Cu} = require("chrome");
|
||||
|
||||
let ToolDefinitions = require("main").Tools;
|
||||
let {CssLogic} = require("devtools/styleinspector/css-logic");
|
||||
let {ELEMENT_STYLE} = require("devtools/server/actors/styles");
|
||||
let promise = require("sdk/core/promise");
|
||||
let {EventEmitter} = require("devtools/shared/event-emitter");
|
||||
const ToolDefinitions = require("main").Tools;
|
||||
const {CssLogic} = require("devtools/styleinspector/css-logic");
|
||||
const {ELEMENT_STYLE} = require("devtools/server/actors/styles");
|
||||
const promise = require("sdk/core/promise");
|
||||
const {EventEmitter} = require("devtools/shared/event-emitter");
|
||||
const {OutputParser} = require("devtools/output-parser");
|
||||
const {Tooltip} = require("devtools/shared/widgets/Tooltip");
|
||||
const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/styleeditor/utils");
|
||||
const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PluralForm.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Templater.jsm");
|
||||
|
||||
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
|
||||
const FILTER_CHANGED_TIMEOUT = 300;
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
|
||||
/**
|
||||
* Helper for long-running processes that should yield occasionally to
|
||||
* the mainloop.
|
||||
@ -183,7 +180,7 @@ function CssHtmlTree(aStyleInspector, aPageStyle)
|
||||
// Properties preview tooltip
|
||||
this.tooltip = new Tooltip(this.styleInspector.inspector.panelDoc);
|
||||
this.tooltip.startTogglingOnHover(this.propertyContainer,
|
||||
this._buildTooltipContent.bind(this));
|
||||
this._onTooltipTargetHover.bind(this));
|
||||
|
||||
this._buildContextMenu();
|
||||
this.createStyleViews();
|
||||
@ -514,32 +511,39 @@ CssHtmlTree.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Verify that target is indeed a css value we want a tooltip on, and if yes
|
||||
* prepare some content for the tooltip
|
||||
* Executed by the tooltip when the pointer hovers over an element of the view.
|
||||
* Used to decide whether the tooltip should be shown or not and to actually
|
||||
* put content in it.
|
||||
* Checks if the hovered target is a css value we support tooltips for.
|
||||
*/
|
||||
_buildTooltipContent: function(target)
|
||||
_onTooltipTargetHover: function(target)
|
||||
{
|
||||
let inspector = this.styleInspector.inspector;
|
||||
|
||||
// Test for image url
|
||||
if (target.classList.contains("theme-link")) {
|
||||
if (target.classList.contains("theme-link") && inspector.hasUrlToImageDataResolver) {
|
||||
let propValue = target.parentNode;
|
||||
let propName = propValue.parentNode.querySelector(".property-name");
|
||||
if (propName.textContent === "background-image") {
|
||||
this.tooltip.setCssBackgroundImageContent(propValue.textContent);
|
||||
return true;
|
||||
let maxDim = Services.prefs.getIntPref("devtools.inspector.imagePreviewTooltipSize");
|
||||
let uri = CssLogic.getBackgroundImageUriFromProperty(propValue.textContent);
|
||||
return this.tooltip.setRelativeImageContent(uri, inspector.inspector, maxDim);
|
||||
}
|
||||
}
|
||||
|
||||
// Test for css transform
|
||||
if (target.classList.contains("property-value")) {
|
||||
let def = promise.defer();
|
||||
let propValue = target;
|
||||
let propName = target.parentNode.querySelector(".property-name");
|
||||
if (propName.textContent === "transform") {
|
||||
this.tooltip.setCssTransformContent(propValue.textContent,
|
||||
this.pageStyle, this.viewedElement).then(def.resolve);
|
||||
return def.promise;
|
||||
return this.tooltip.setCssTransformContent(propValue.textContent,
|
||||
this.pageStyle, this.viewedElement);
|
||||
}
|
||||
}
|
||||
|
||||
// If the target isn't one that should receive a tooltip, signal it by rejecting
|
||||
// a promise
|
||||
return promise.reject();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -8,13 +8,13 @@
|
||||
|
||||
const {Cc, Ci, Cu} = require("chrome");
|
||||
const promise = require("sdk/core/promise");
|
||||
|
||||
const {CssLogic} = require("devtools/styleinspector/css-logic");
|
||||
const {InplaceEditor, editableField, editableItem} = require("devtools/shared/inplace-editor");
|
||||
const {ELEMENT_STYLE, PSEUDO_ELEMENTS} = require("devtools/server/actors/styles");
|
||||
const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
const {Tooltip, SwatchColorPickerTooltip} = require("devtools/shared/widgets/Tooltip");
|
||||
const {OutputParser} = require("devtools/output-parser");
|
||||
const { PrefObserver, PREF_ORIG_SOURCES } = require("devtools/styleeditor/utils");
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
@ -22,8 +22,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
const { PrefObserver, PREF_ORIG_SOURCES } = require("devtools/styleeditor/utils");
|
||||
|
||||
/**
|
||||
* These regular expressions are adapted from firebug's css.js, and are
|
||||
* used to parse CSSStyleDeclaration's cssText attribute.
|
||||
@ -116,8 +114,7 @@ function createDummyDocument() {
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function ElementStyle(aElement, aStore, aPageStyle)
|
||||
{
|
||||
function ElementStyle(aElement, aStore, aPageStyle) {
|
||||
this.element = aElement;
|
||||
this.store = aStore || {};
|
||||
this.pageStyle = aPageStyle;
|
||||
@ -142,11 +139,11 @@ function ElementStyle(aElement, aStore, aPageStyle)
|
||||
return this.dummyElement;
|
||||
}).then(null, promiseWarn);
|
||||
}
|
||||
|
||||
// We're exporting _ElementStyle for unit tests.
|
||||
exports._ElementStyle = ElementStyle;
|
||||
|
||||
ElementStyle.prototype = {
|
||||
|
||||
// The element we're looking at.
|
||||
element: null,
|
||||
|
||||
@ -154,8 +151,7 @@ ElementStyle.prototype = {
|
||||
// to figure out how shorthand properties will be parsed.
|
||||
dummyElement: null,
|
||||
|
||||
destroy: function()
|
||||
{
|
||||
destroy: function() {
|
||||
this.dummyElement = null;
|
||||
this.dummyElementPromise.then(dummyElement => {
|
||||
if (dummyElement.parentNode) {
|
||||
@ -169,8 +165,7 @@ ElementStyle.prototype = {
|
||||
* Called by the Rule object when it has been changed through the
|
||||
* setProperty* methods.
|
||||
*/
|
||||
_changed: function ElementStyle_changed()
|
||||
{
|
||||
_changed: function() {
|
||||
if (this.onChanged) {
|
||||
this.onChanged();
|
||||
}
|
||||
@ -183,8 +178,7 @@ ElementStyle.prototype = {
|
||||
* Returns a promise that will be resolved when the elementStyle is
|
||||
* ready.
|
||||
*/
|
||||
populate: function ElementStyle_populate()
|
||||
{
|
||||
populate: function() {
|
||||
let populated = this.pageStyle.getApplied(this.element, {
|
||||
inherited: true,
|
||||
matchedSelectors: true
|
||||
@ -224,8 +218,7 @@ ElementStyle.prototype = {
|
||||
/**
|
||||
* Put pseudo elements in front of others.
|
||||
*/
|
||||
_sortRulesForPseudoElement: function ElementStyle_sortRulesForPseudoElement()
|
||||
{
|
||||
_sortRulesForPseudoElement: function() {
|
||||
this.rules = this.rules.sort((a, b) => {
|
||||
return (a.pseudoElement || "z") > (b.pseudoElement || "z");
|
||||
});
|
||||
@ -240,8 +233,7 @@ ElementStyle.prototype = {
|
||||
*
|
||||
* @return {bool} true if we added the rule.
|
||||
*/
|
||||
_maybeAddRule: function ElementStyle_maybeAddRule(aOptions)
|
||||
{
|
||||
_maybeAddRule: function(aOptions) {
|
||||
// If we've already included this domRule (for example, when a
|
||||
// common selector is inherited), ignore it.
|
||||
if (aOptions.rule &&
|
||||
@ -284,8 +276,7 @@ ElementStyle.prototype = {
|
||||
/**
|
||||
* Calls markOverridden with all supported pseudo elements
|
||||
*/
|
||||
markOverriddenAll: function ElementStyle_markOverriddenAll()
|
||||
{
|
||||
markOverriddenAll: function() {
|
||||
this.markOverridden();
|
||||
for (let pseudo of PSEUDO_ELEMENTS) {
|
||||
this.markOverridden(pseudo);
|
||||
@ -299,8 +290,7 @@ ElementStyle.prototype = {
|
||||
* Which pseudo element to flag as overridden.
|
||||
* Empty string or undefined will default to no pseudo element.
|
||||
*/
|
||||
markOverridden: function ElementStyle_markOverridden(pseudo="")
|
||||
{
|
||||
markOverridden: function(pseudo="") {
|
||||
// Gather all the text properties applied by these rules, ordered
|
||||
// from more- to less-specific.
|
||||
let textProps = [];
|
||||
@ -380,8 +370,7 @@ ElementStyle.prototype = {
|
||||
* @return {bool} true if the TextProperty's overridden state (or any of its
|
||||
* computed properties overridden state) changed.
|
||||
*/
|
||||
_updatePropertyOverridden: function ElementStyle_updatePropertyOverridden(aProp)
|
||||
{
|
||||
_updatePropertyOverridden: function(aProp) {
|
||||
let overridden = true;
|
||||
let dirty = false;
|
||||
for each (let computedProp in aProp.computed) {
|
||||
@ -410,8 +399,7 @@ ElementStyle.prototype = {
|
||||
* the rule applies directly to the current element.
|
||||
* @constructor
|
||||
*/
|
||||
function Rule(aElementStyle, aOptions)
|
||||
{
|
||||
function Rule(aElementStyle, aOptions) {
|
||||
this.elementStyle = aElementStyle;
|
||||
this.domRule = aOptions.rule || null;
|
||||
this.style = aOptions.rule;
|
||||
@ -437,8 +425,7 @@ function Rule(aElementStyle, aOptions)
|
||||
Rule.prototype = {
|
||||
mediaText: "",
|
||||
|
||||
get title()
|
||||
{
|
||||
get title() {
|
||||
if (this._title) {
|
||||
return this._title;
|
||||
}
|
||||
@ -451,8 +438,7 @@ Rule.prototype = {
|
||||
return this._title;
|
||||
},
|
||||
|
||||
get inheritedSource()
|
||||
{
|
||||
get inheritedSource() {
|
||||
if (this._inheritedSource) {
|
||||
return this._inheritedSource;
|
||||
}
|
||||
@ -468,32 +454,28 @@ Rule.prototype = {
|
||||
return this._inheritedSource;
|
||||
},
|
||||
|
||||
get selectorText()
|
||||
{
|
||||
get selectorText() {
|
||||
return this.domRule.selectors ? this.domRule.selectors.join(", ") : CssLogic.l10n("rule.sourceElement");
|
||||
},
|
||||
|
||||
/**
|
||||
* The rule's stylesheet.
|
||||
*/
|
||||
get sheet()
|
||||
{
|
||||
get sheet() {
|
||||
return this.domRule ? this.domRule.parentStyleSheet : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* The rule's line within a stylesheet
|
||||
*/
|
||||
get ruleLine()
|
||||
{
|
||||
get ruleLine() {
|
||||
return this.domRule ? this.domRule.line : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* The rule's column within a stylesheet
|
||||
*/
|
||||
get ruleColumn()
|
||||
{
|
||||
get ruleColumn() {
|
||||
return this.domRule ? this.domRule.column : null;
|
||||
},
|
||||
|
||||
@ -504,8 +486,7 @@ Rule.prototype = {
|
||||
* @return {Promise}
|
||||
* Promise which resolves with location as a string.
|
||||
*/
|
||||
getOriginalSourceString: function Rule_getOriginalSourceString()
|
||||
{
|
||||
getOriginalSourceString: function() {
|
||||
if (this._originalSourceString) {
|
||||
return promise.resolve(this._originalSourceString);
|
||||
}
|
||||
@ -523,8 +504,7 @@ Rule.prototype = {
|
||||
* @param {object} aOptions
|
||||
* Creation options. See the Rule constructor for documentation.
|
||||
*/
|
||||
matches: function Rule_matches(aOptions)
|
||||
{
|
||||
matches: function(aOptions) {
|
||||
return this.style === aOptions.rule;
|
||||
},
|
||||
|
||||
@ -540,8 +520,7 @@ Rule.prototype = {
|
||||
* @param {TextProperty} aSiblingProp
|
||||
* Optional, property next to which the new property will be added.
|
||||
*/
|
||||
createProperty: function Rule_createProperty(aName, aValue, aPriority, aSiblingProp)
|
||||
{
|
||||
createProperty: function(aName, aValue, aPriority, aSiblingProp) {
|
||||
let prop = new TextProperty(this, aName, aValue, aPriority);
|
||||
|
||||
if (aSiblingProp) {
|
||||
@ -566,8 +545,7 @@ Rule.prototype = {
|
||||
* when calling from setPropertyValue & setPropertyName to signify
|
||||
* that the property should be saved in store.userProperties.
|
||||
*/
|
||||
applyProperties: function Rule_applyProperties(aModifications, aName)
|
||||
{
|
||||
applyProperties: function(aModifications, aName) {
|
||||
this.elementStyle.markOverriddenAll();
|
||||
|
||||
if (!aModifications) {
|
||||
@ -652,8 +630,7 @@ Rule.prototype = {
|
||||
* @param {string} aName
|
||||
* The new property name (such as "background" or "border-top").
|
||||
*/
|
||||
setPropertyName: function Rule_setPropertyName(aProperty, aName)
|
||||
{
|
||||
setPropertyName: function(aProperty, aName) {
|
||||
if (aName === aProperty.name) {
|
||||
return;
|
||||
}
|
||||
@ -673,8 +650,7 @@ Rule.prototype = {
|
||||
* @param {string} aPriority
|
||||
* The property's priority (either "important" or an empty string).
|
||||
*/
|
||||
setPropertyValue: function Rule_setPropertyValue(aProperty, aValue, aPriority)
|
||||
{
|
||||
setPropertyValue: function(aProperty, aValue, aPriority) {
|
||||
if (aValue === aProperty.value && aPriority === aProperty.priority) {
|
||||
return;
|
||||
}
|
||||
@ -687,8 +663,7 @@ Rule.prototype = {
|
||||
/**
|
||||
* Disables or enables given TextProperty.
|
||||
*/
|
||||
setPropertyEnabled: function Rule_enableProperty(aProperty, aValue)
|
||||
{
|
||||
setPropertyEnabled: function(aProperty, aValue) {
|
||||
aProperty.enabled = !!aValue;
|
||||
let modifications = this.style.startModifyingProperties();
|
||||
if (!aProperty.enabled) {
|
||||
@ -701,8 +676,7 @@ Rule.prototype = {
|
||||
* Remove a given TextProperty from the rule and update the rule
|
||||
* accordingly.
|
||||
*/
|
||||
removeProperty: function Rule_removeProperty(aProperty)
|
||||
{
|
||||
removeProperty: function(aProperty) {
|
||||
this.textProps = this.textProps.filter(function(prop) prop != aProperty);
|
||||
let modifications = this.style.startModifyingProperties();
|
||||
modifications.removeProperty(aProperty.name);
|
||||
@ -715,8 +689,7 @@ Rule.prototype = {
|
||||
* Get the list of TextProperties from the style. Needs
|
||||
* to parse the style's cssText.
|
||||
*/
|
||||
_getTextProperties: function Rule_getTextProperties()
|
||||
{
|
||||
_getTextProperties: function() {
|
||||
let textProps = [];
|
||||
let store = this.elementStyle.store;
|
||||
let props = parseCSSText(this.style.cssText);
|
||||
@ -736,8 +709,7 @@ Rule.prototype = {
|
||||
/**
|
||||
* Return the list of disabled properties from the store for this rule.
|
||||
*/
|
||||
_getDisabledProperties: function Rule_getDisabledProperties()
|
||||
{
|
||||
_getDisabledProperties: function() {
|
||||
let store = this.elementStyle.store;
|
||||
|
||||
// Include properties from the disabled property store, if any.
|
||||
@ -762,8 +734,7 @@ Rule.prototype = {
|
||||
* Reread the current state of the rules and rebuild text
|
||||
* properties as needed.
|
||||
*/
|
||||
refresh: function Rule_refresh(aOptions)
|
||||
{
|
||||
refresh: function(aOptions) {
|
||||
this.matchedSelectors = aOptions.matchedSelectors || [];
|
||||
let newTextProps = this._getTextProperties();
|
||||
|
||||
@ -825,7 +796,7 @@ Rule.prototype = {
|
||||
* @return {bool} true if a property was updated, false if no properties
|
||||
* were updated.
|
||||
*/
|
||||
_updateTextProperty: function Rule__updateTextProperty(aNewProp) {
|
||||
_updateTextProperty: function(aNewProp) {
|
||||
let match = { rank: 0, prop: null };
|
||||
|
||||
for each (let prop in this.textProps) {
|
||||
@ -887,8 +858,7 @@ Rule.prototype = {
|
||||
* The text property that will be left to focus on a sibling.
|
||||
*
|
||||
*/
|
||||
editClosestTextProperty: function Rule__editClosestTextProperty(aTextProperty)
|
||||
{
|
||||
editClosestTextProperty: function(aTextProperty) {
|
||||
let index = this.textProps.indexOf(aTextProperty);
|
||||
let previous = false;
|
||||
|
||||
@ -930,8 +900,7 @@ Rule.prototype = {
|
||||
* The property's priority (either "important" or an empty string).
|
||||
*
|
||||
*/
|
||||
function TextProperty(aRule, aName, aValue, aPriority)
|
||||
{
|
||||
function TextProperty(aRule, aName, aValue, aPriority) {
|
||||
this.rule = aRule;
|
||||
this.name = aName;
|
||||
this.value = aValue;
|
||||
@ -945,8 +914,7 @@ TextProperty.prototype = {
|
||||
* Update the editor associated with this text property,
|
||||
* if any.
|
||||
*/
|
||||
updateEditor: function TextProperty_updateEditor()
|
||||
{
|
||||
updateEditor: function() {
|
||||
if (this.editor) {
|
||||
this.editor.update();
|
||||
}
|
||||
@ -955,8 +923,7 @@ TextProperty.prototype = {
|
||||
/**
|
||||
* Update the list of computed properties for this text property.
|
||||
*/
|
||||
updateComputed: function TextProperty_updateComputed()
|
||||
{
|
||||
updateComputed: function() {
|
||||
if (!this.name) {
|
||||
return;
|
||||
}
|
||||
@ -988,8 +955,7 @@ TextProperty.prototype = {
|
||||
* @param {TextProperty} aOther
|
||||
* The other TextProperty instance.
|
||||
*/
|
||||
set: function TextProperty_set(aOther)
|
||||
{
|
||||
set: function(aOther) {
|
||||
let changed = false;
|
||||
for (let item of ["name", "value", "priority", "enabled"]) {
|
||||
if (this[item] != aOther[item]) {
|
||||
@ -1003,28 +969,24 @@ TextProperty.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
setValue: function TextProperty_setValue(aValue, aPriority)
|
||||
{
|
||||
setValue: function(aValue, aPriority) {
|
||||
this.rule.setPropertyValue(this, aValue, aPriority);
|
||||
this.updateEditor();
|
||||
},
|
||||
|
||||
setName: function TextProperty_setName(aName)
|
||||
{
|
||||
setName: function(aName) {
|
||||
this.rule.setPropertyName(this, aName);
|
||||
this.updateEditor();
|
||||
},
|
||||
|
||||
setEnabled: function TextProperty_setEnabled(aValue)
|
||||
{
|
||||
setEnabled: function(aValue) {
|
||||
this.rule.setPropertyEnabled(this, aValue);
|
||||
this.updateEditor();
|
||||
},
|
||||
|
||||
remove: function TextProperty_remove()
|
||||
{
|
||||
remove: function() {
|
||||
this.rule.removeProperty(this);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1061,8 +1023,7 @@ TextProperty.prototype = {
|
||||
* The PageStyleFront for communicating with the remote server.
|
||||
* @constructor
|
||||
*/
|
||||
function CssRuleView(aInspector, aDoc, aStore, aPageStyle)
|
||||
{
|
||||
function CssRuleView(aInspector, aDoc, aStore, aPageStyle) {
|
||||
this.inspector = aInspector;
|
||||
this.doc = aDoc;
|
||||
this.store = aStore || {};
|
||||
@ -1097,7 +1058,7 @@ function CssRuleView(aInspector, aDoc, aStore, aPageStyle)
|
||||
// Create a tooltip for previewing things in the rule view (images for now)
|
||||
this.previewTooltip = new Tooltip(this.inspector.panelDoc);
|
||||
this.previewTooltip.startTogglingOnHover(this.element,
|
||||
this._buildTooltipContent.bind(this));
|
||||
this._onTooltipTargetHover.bind(this));
|
||||
|
||||
// Also create a more complex tooltip for editing colors with the spectrum
|
||||
// color picker
|
||||
@ -1149,10 +1110,12 @@ CssRuleView.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Verify that target is indeed a css value we want a tooltip on, and if yes
|
||||
* prepare some content for the tooltip
|
||||
* Executed by the tooltip when the pointer hovers over an element of the view.
|
||||
* Used to decide whether the tooltip should be shown or not and to actually
|
||||
* put content in it.
|
||||
* Checks if the hovered target is a css value we support tooltips for.
|
||||
*/
|
||||
_buildTooltipContent: function(target) {
|
||||
_onTooltipTargetHover: function(target) {
|
||||
let property = target.textProperty, def = promise.defer(), hasTooltip = false;
|
||||
|
||||
// Test for css transform
|
||||
@ -1163,19 +1126,26 @@ CssRuleView.prototype = {
|
||||
}
|
||||
|
||||
// Test for image
|
||||
let isImageHref = target.classList.contains("theme-link") &&
|
||||
target.parentNode.classList.contains("ruleview-propertyvalue");
|
||||
if (isImageHref) {
|
||||
property = target.parentNode.textProperty;
|
||||
this.previewTooltip.setCssBackgroundImageContent(property.value,
|
||||
property.rule.domRule.href);
|
||||
def.resolve();
|
||||
hasTooltip = true;
|
||||
if (this.inspector.hasUrlToImageDataResolver) {
|
||||
let isImageHref = target.classList.contains("theme-link") &&
|
||||
target.parentNode.classList.contains("ruleview-propertyvalue");
|
||||
if (isImageHref) {
|
||||
property = target.parentNode.textProperty;
|
||||
|
||||
let maxDim = Services.prefs.getIntPref("devtools.inspector.imagePreviewTooltipSize");
|
||||
let uri = CssLogic.getBackgroundImageUriFromProperty(property.value,
|
||||
property.rule.domRule.href);
|
||||
this.previewTooltip.setRelativeImageContent(uri,
|
||||
this.inspector.inspector, maxDim).then(def.resolve);
|
||||
hasTooltip = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasTooltip) {
|
||||
this.colorPicker.revert();
|
||||
this.colorPicker.hide();
|
||||
} else {
|
||||
def.reject();
|
||||
}
|
||||
|
||||
return def.promise;
|
||||
@ -1225,8 +1195,7 @@ CssRuleView.prototype = {
|
||||
/**
|
||||
* Select all text.
|
||||
*/
|
||||
_onSelectAll: function()
|
||||
{
|
||||
_onSelectAll: function() {
|
||||
let win = this.doc.defaultView;
|
||||
let selection = win.getSelection();
|
||||
|
||||
@ -1239,8 +1208,7 @@ CssRuleView.prototype = {
|
||||
* @param {Event} event
|
||||
* The event object.
|
||||
*/
|
||||
_onCopy: function(event)
|
||||
{
|
||||
_onCopy: function(event) {
|
||||
try {
|
||||
let target = event.target;
|
||||
let text;
|
||||
@ -1279,8 +1247,7 @@ CssRuleView.prototype = {
|
||||
/**
|
||||
* Toggle the original sources pref.
|
||||
*/
|
||||
_onToggleOrigSources: function()
|
||||
{
|
||||
_onToggleOrigSources: function() {
|
||||
let isEnabled = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
|
||||
Services.prefs.setBoolPref(PREF_ORIG_SOURCES, !isEnabled);
|
||||
},
|
||||
@ -1305,8 +1272,7 @@ CssRuleView.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_onSourcePrefChanged: function()
|
||||
{
|
||||
_onSourcePrefChanged: function() {
|
||||
if (this.menuitemSources) {
|
||||
let isEnabled = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
|
||||
this.menuitemSources.setAttribute("checked", isEnabled);
|
||||
@ -1320,8 +1286,7 @@ CssRuleView.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function CssRuleView_destroy()
|
||||
{
|
||||
destroy: function() {
|
||||
this.clear();
|
||||
|
||||
gDummyPromise = null;
|
||||
@ -1378,8 +1343,7 @@ CssRuleView.prototype = {
|
||||
* @param {NodeActor} aElement
|
||||
* The node whose style rules we'll inspect.
|
||||
*/
|
||||
highlight: function CssRuleView_highlight(aElement)
|
||||
{
|
||||
highlight: function(aElement) {
|
||||
if (this._viewedElement === aElement) {
|
||||
return promise.resolve(undefined);
|
||||
}
|
||||
@ -1407,8 +1371,7 @@ CssRuleView.prototype = {
|
||||
/**
|
||||
* Update the rules for the currently highlighted element.
|
||||
*/
|
||||
nodeChanged: function CssRuleView_nodeChanged()
|
||||
{
|
||||
nodeChanged: function() {
|
||||
// Ignore refreshes during editing or when no element is selected.
|
||||
if (this.isEditing || !this._elementStyle) {
|
||||
return;
|
||||
@ -1439,8 +1402,7 @@ CssRuleView.prototype = {
|
||||
/**
|
||||
* Show the user that the rule view has no node selected.
|
||||
*/
|
||||
_showEmpty: function CssRuleView_showEmpty()
|
||||
{
|
||||
_showEmpty: function() {
|
||||
if (this.doc.getElementById("noResults") > 0) {
|
||||
return;
|
||||
}
|
||||
@ -1454,8 +1416,7 @@ CssRuleView.prototype = {
|
||||
/**
|
||||
* Clear the rules.
|
||||
*/
|
||||
_clearRules: function CssRuleView_clearRules()
|
||||
{
|
||||
_clearRules: function() {
|
||||
while (this.element.hasChildNodes()) {
|
||||
this.element.removeChild(this.element.lastChild);
|
||||
}
|
||||
@ -1464,8 +1425,7 @@ CssRuleView.prototype = {
|
||||
/**
|
||||
* Clear the rule view.
|
||||
*/
|
||||
clear: function CssRuleView_clear()
|
||||
{
|
||||
clear: function() {
|
||||
this._clearRules();
|
||||
this._viewedElement = null;
|
||||
this._elementStyle = null;
|
||||
@ -1478,8 +1438,7 @@ CssRuleView.prototype = {
|
||||
* Called when the user has made changes to the ElementStyle.
|
||||
* Emits an event that clients can listen to.
|
||||
*/
|
||||
_changed: function CssRuleView_changed()
|
||||
{
|
||||
_changed: function() {
|
||||
var evt = this.doc.createEvent("Events");
|
||||
evt.initEvent("CssRuleViewChanged", true, false);
|
||||
this.element.dispatchEvent(evt);
|
||||
@ -1488,8 +1447,7 @@ CssRuleView.prototype = {
|
||||
/**
|
||||
* Text for header that shows above rules for this element
|
||||
*/
|
||||
get selectedElementLabel ()
|
||||
{
|
||||
get selectedElementLabel() {
|
||||
if (this._selectedElementLabel) {
|
||||
return this._selectedElementLabel;
|
||||
}
|
||||
@ -1500,8 +1458,7 @@ CssRuleView.prototype = {
|
||||
/**
|
||||
* Text for header that shows above rules for pseudo elements
|
||||
*/
|
||||
get pseudoElementLabel ()
|
||||
{
|
||||
get pseudoElementLabel() {
|
||||
if (this._pseudoElementLabel) {
|
||||
return this._pseudoElementLabel;
|
||||
}
|
||||
@ -1509,8 +1466,7 @@ CssRuleView.prototype = {
|
||||
return this._pseudoElementLabel;
|
||||
},
|
||||
|
||||
togglePseudoElementVisibility: function(value)
|
||||
{
|
||||
togglePseudoElementVisibility: function(value) {
|
||||
this._showPseudoElements = !!value;
|
||||
let isOpen = this.showPseudoElements;
|
||||
|
||||
@ -1529,8 +1485,7 @@ CssRuleView.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
get showPseudoElements ()
|
||||
{
|
||||
get showPseudoElements() {
|
||||
if (this._showPseudoElements === undefined) {
|
||||
this._showPseudoElements =
|
||||
Services.prefs.getBoolPref("devtools.inspector.show_pseudo_elements");
|
||||
@ -1546,8 +1501,7 @@ CssRuleView.prototype = {
|
||||
/**
|
||||
* Creates editor UI for each of the rules in _elementStyle.
|
||||
*/
|
||||
_createEditors: function CssRuleView_createEditors()
|
||||
{
|
||||
_createEditors: function() {
|
||||
// Run through the current list of rules, attaching
|
||||
// their editors in order. Create editors if needed.
|
||||
let lastInheritedSource = "";
|
||||
@ -1606,8 +1560,7 @@ CssRuleView.prototype = {
|
||||
}
|
||||
|
||||
this.togglePseudoElementVisibility(this.showPseudoElements);
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1619,8 +1572,7 @@ CssRuleView.prototype = {
|
||||
* The Rule object we're editing.
|
||||
* @constructor
|
||||
*/
|
||||
function RuleEditor(aRuleView, aRule)
|
||||
{
|
||||
function RuleEditor(aRuleView, aRule) {
|
||||
this.ruleView = aRuleView;
|
||||
this.doc = this.ruleView.doc;
|
||||
this.rule = aRule;
|
||||
@ -1632,8 +1584,7 @@ function RuleEditor(aRuleView, aRule)
|
||||
}
|
||||
|
||||
RuleEditor.prototype = {
|
||||
_create: function RuleEditor_create()
|
||||
{
|
||||
_create: function() {
|
||||
this.element = this.doc.createElementNS(HTML_NS, "div");
|
||||
this.element.className = "ruleview-rule theme-separator";
|
||||
this.element._ruleEditor = this;
|
||||
@ -1742,8 +1693,7 @@ RuleEditor.prototype = {
|
||||
/**
|
||||
* Update the rule editor with the contents of the rule.
|
||||
*/
|
||||
populate: function RuleEditor_populate()
|
||||
{
|
||||
populate: function() {
|
||||
// Clear out existing viewers.
|
||||
while (this.selectorText.hasChildNodes()) {
|
||||
this.selectorText.removeChild(this.selectorText.lastChild);
|
||||
@ -1797,8 +1747,7 @@ RuleEditor.prototype = {
|
||||
* @return {TextProperty}
|
||||
* The new property
|
||||
*/
|
||||
addProperty: function RuleEditor_addProperty(aName, aValue, aPriority, aSiblingProp)
|
||||
{
|
||||
addProperty: function(aName, aValue, aPriority, aSiblingProp) {
|
||||
let prop = this.rule.createProperty(aName, aValue, aPriority, aSiblingProp);
|
||||
let index = this.rule.textProps.indexOf(prop);
|
||||
let editor = new TextPropertyEditor(this, prop);
|
||||
@ -1829,8 +1778,7 @@ RuleEditor.prototype = {
|
||||
* @param {TextProperty} aSiblingProp
|
||||
* Optional, the property next to which all new props should be added.
|
||||
*/
|
||||
addProperties: function RuleEditor_addProperties(aProperties, aSiblingProp)
|
||||
{
|
||||
addProperties: function(aProperties, aSiblingProp) {
|
||||
if (!aProperties || !aProperties.length) {
|
||||
return;
|
||||
}
|
||||
@ -1853,8 +1801,7 @@ RuleEditor.prototype = {
|
||||
* name is given, we'll create a real TextProperty and add it to the
|
||||
* rule.
|
||||
*/
|
||||
newProperty: function RuleEditor_newProperty()
|
||||
{
|
||||
newProperty: function() {
|
||||
// If we're already creating a new property, ignore this.
|
||||
if (!this.closeBrace.hasAttribute("tabindex")) {
|
||||
return;
|
||||
@ -1898,8 +1845,7 @@ RuleEditor.prototype = {
|
||||
* @param {bool} aCommit
|
||||
* True if the value should be committed.
|
||||
*/
|
||||
_onNewProperty: function RuleEditor__onNewProperty(aValue, aCommit)
|
||||
{
|
||||
_onNewProperty: function(aValue, aCommit) {
|
||||
if (!aValue || !aCommit) {
|
||||
return;
|
||||
}
|
||||
@ -1924,8 +1870,7 @@ RuleEditor.prototype = {
|
||||
* added, since we want to wait until after the inplace editor `destroy`
|
||||
* event has been fired to keep consistent UI state.
|
||||
*/
|
||||
_newPropertyDestroy: function RuleEditor__newPropertyDestroy()
|
||||
{
|
||||
_newPropertyDestroy: function() {
|
||||
// We're done, make the close brace focusable again.
|
||||
this.closeBrace.setAttribute("tabindex", "0");
|
||||
|
||||
@ -1951,8 +1896,7 @@ RuleEditor.prototype = {
|
||||
* The text property to edit.
|
||||
* @constructor
|
||||
*/
|
||||
function TextPropertyEditor(aRuleEditor, aProperty)
|
||||
{
|
||||
function TextPropertyEditor(aRuleEditor, aProperty) {
|
||||
this.ruleEditor = aRuleEditor;
|
||||
this.doc = this.ruleEditor.doc;
|
||||
this.popup = this.ruleEditor.ruleView.popup;
|
||||
@ -1991,8 +1935,7 @@ TextPropertyEditor.prototype = {
|
||||
/**
|
||||
* Create the property editor's DOM.
|
||||
*/
|
||||
_create: function TextPropertyEditor_create()
|
||||
{
|
||||
_create: function() {
|
||||
this.element = this.doc.createElementNS(HTML_NS, "li");
|
||||
this.element.classList.add("ruleview-property");
|
||||
|
||||
@ -2119,8 +2062,7 @@ TextPropertyEditor.prototype = {
|
||||
* @param {string} relativePath the path to resolve
|
||||
* @return {string} the resolved path.
|
||||
*/
|
||||
resolveURI: function(relativePath)
|
||||
{
|
||||
resolveURI: function(relativePath) {
|
||||
if (this.sheetURI) {
|
||||
relativePath = this.sheetURI.resolve(relativePath);
|
||||
}
|
||||
@ -2131,8 +2073,7 @@ TextPropertyEditor.prototype = {
|
||||
* Check the property value to find an external resource (if any).
|
||||
* @return {string} the URI in the property value, or null if there is no match.
|
||||
*/
|
||||
getResourceURI: function()
|
||||
{
|
||||
getResourceURI: function() {
|
||||
let val = this.prop.value;
|
||||
let uriMatch = CSS_RESOURCE_RE.exec(val);
|
||||
let uri = null;
|
||||
@ -2147,8 +2088,7 @@ TextPropertyEditor.prototype = {
|
||||
/**
|
||||
* Populate the span based on changes to the TextProperty.
|
||||
*/
|
||||
update: function TextPropertyEditor_update()
|
||||
{
|
||||
update: function() {
|
||||
if (this.prop.enabled) {
|
||||
this.enable.style.removeProperty("visibility");
|
||||
this.enable.setAttribute("checked", "");
|
||||
@ -2216,8 +2156,7 @@ TextPropertyEditor.prototype = {
|
||||
this._updateComputed();
|
||||
},
|
||||
|
||||
_onStartEditing: function TextPropertyEditor_onStartEditing()
|
||||
{
|
||||
_onStartEditing: function() {
|
||||
this.element.classList.remove("ruleview-overridden");
|
||||
this._livePreview(this.prop.value);
|
||||
},
|
||||
@ -2225,8 +2164,7 @@ TextPropertyEditor.prototype = {
|
||||
/**
|
||||
* Populate the list of computed styles.
|
||||
*/
|
||||
_updateComputed: function TextPropertyEditor_updateComputed()
|
||||
{
|
||||
_updateComputed: function () {
|
||||
// Clear out existing viewers.
|
||||
while (this.computed.hasChildNodes()) {
|
||||
this.computed.removeChild(this.computed.lastChild);
|
||||
@ -2284,8 +2222,7 @@ TextPropertyEditor.prototype = {
|
||||
/**
|
||||
* Handles clicks on the disabled property.
|
||||
*/
|
||||
_onEnableClicked: function TextPropertyEditor_onEnableClicked(aEvent)
|
||||
{
|
||||
_onEnableClicked: function(aEvent) {
|
||||
let checked = this.enable.hasAttribute("checked");
|
||||
if (checked) {
|
||||
this.enable.removeAttribute("checked");
|
||||
@ -2299,8 +2236,7 @@ TextPropertyEditor.prototype = {
|
||||
/**
|
||||
* Handles clicks on the computed property expander.
|
||||
*/
|
||||
_onExpandClicked: function TextPropertyEditor_onExpandClicked(aEvent)
|
||||
{
|
||||
_onExpandClicked: function(aEvent) {
|
||||
this.computed.classList.toggle("styleinspector-open");
|
||||
if (this.computed.classList.contains("styleinspector-open")) {
|
||||
this.expander.setAttribute("open", "true");
|
||||
@ -2320,8 +2256,7 @@ TextPropertyEditor.prototype = {
|
||||
* @param {boolean} aCommit
|
||||
* True if the change should be applied.
|
||||
*/
|
||||
_onNameDone: function TextPropertyEditor_onNameDone(aValue, aCommit)
|
||||
{
|
||||
_onNameDone: function(aValue, aCommit) {
|
||||
if (aCommit) {
|
||||
// Unlike the value editor, if a name is empty the entire property
|
||||
// should always be removed.
|
||||
@ -2349,8 +2284,7 @@ TextPropertyEditor.prototype = {
|
||||
* Remove property from style and the editors from DOM.
|
||||
* Begin editing next available property.
|
||||
*/
|
||||
remove: function TextPropertyEditor_remove()
|
||||
{
|
||||
remove: function() {
|
||||
if (this._swatchSpans && this._swatchSpans.length) {
|
||||
for (let span of this._swatchSpans) {
|
||||
this.ruleEditor.ruleView.colorPicker.removeSwatch(span);
|
||||
@ -2372,8 +2306,7 @@ TextPropertyEditor.prototype = {
|
||||
* @param {bool} aCommit
|
||||
* True if the change should be applied.
|
||||
*/
|
||||
_onValueDone: function PropertyEditor_onValueDone(aValue, aCommit)
|
||||
{
|
||||
_onValueDone: function(aValue, aCommit) {
|
||||
if (!aCommit) {
|
||||
// A new property should be removed when escape is pressed.
|
||||
if (this.removeOnRevert) {
|
||||
@ -2425,8 +2358,7 @@ TextPropertyEditor.prototype = {
|
||||
* propertiesToAdd: An array with additional properties, following the
|
||||
* parseCSSText format of {name,value,priority}
|
||||
*/
|
||||
_getValueAndExtraProperties: function PropetyEditor_getValueAndExtraProperties(aValue) {
|
||||
|
||||
_getValueAndExtraProperties: function(aValue) {
|
||||
// The inplace editor will prevent manual typing of multiple properties,
|
||||
// but we need to deal with the case during a paste event.
|
||||
// Adding multiple properties inside of value editor sets value with the
|
||||
@ -2462,8 +2394,7 @@ TextPropertyEditor.prototype = {
|
||||
};
|
||||
},
|
||||
|
||||
_applyNewValue: function PropetyEditor_applyNewValue(aValue)
|
||||
{
|
||||
_applyNewValue: function(aValue) {
|
||||
let val = parseCSSValue(aValue);
|
||||
// Any property should be removed if has an empty value.
|
||||
if (val.value.trim() === "") {
|
||||
@ -2482,8 +2413,7 @@ TextPropertyEditor.prototype = {
|
||||
* @param {string} [aValue]
|
||||
* The value to set the current property to.
|
||||
*/
|
||||
_livePreview: function TextPropertyEditor_livePreview(aValue)
|
||||
{
|
||||
_livePreview: function(aValue) {
|
||||
// Since function call is throttled, we need to make sure we are still editing
|
||||
if (!this.editing) {
|
||||
return;
|
||||
@ -2506,8 +2436,7 @@ TextPropertyEditor.prototype = {
|
||||
*
|
||||
* @return {bool} true if the property value is valid, false otherwise.
|
||||
*/
|
||||
isValid: function TextPropertyEditor_isValid(aValue)
|
||||
{
|
||||
isValid: function(aValue) {
|
||||
let name = this.prop.name;
|
||||
let value = typeof aValue == "undefined" ? this.prop.value : aValue;
|
||||
let val = parseCSSValue(value);
|
||||
@ -2534,8 +2463,7 @@ TextPropertyEditor.prototype = {
|
||||
* Store of CSSStyleDeclarations mapped to properties that have been changed by
|
||||
* the user.
|
||||
*/
|
||||
function UserProperties()
|
||||
{
|
||||
function UserProperties() {
|
||||
this.map = new Map();
|
||||
}
|
||||
|
||||
@ -2607,7 +2535,7 @@ UserProperties.prototype = {
|
||||
|
||||
getKey: function(aStyle) {
|
||||
return aStyle.href + ":" + aStyle.line;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -2624,8 +2552,7 @@ UserProperties.prototype = {
|
||||
* @param {object} aAttributes
|
||||
* A set of attributes to set on the node.
|
||||
*/
|
||||
function createChild(aParent, aTag, aAttributes)
|
||||
{
|
||||
function createChild(aParent, aTag, aAttributes) {
|
||||
let elt = aParent.ownerDocument.createElementNS(HTML_NS, aTag);
|
||||
for (let attr in aAttributes) {
|
||||
if (aAttributes.hasOwnProperty(attr)) {
|
||||
@ -2642,8 +2569,7 @@ function createChild(aParent, aTag, aAttributes)
|
||||
return elt;
|
||||
}
|
||||
|
||||
function createMenuItem(aMenu, aAttributes)
|
||||
{
|
||||
function createMenuItem(aMenu, aAttributes) {
|
||||
let item = aMenu.ownerDocument.createElementNS(XUL_NS, "menuitem");
|
||||
|
||||
item.setAttribute("label", _strings.GetStringFromName(aAttributes.label));
|
||||
@ -2655,20 +2581,17 @@ function createMenuItem(aMenu, aAttributes)
|
||||
return item;
|
||||
}
|
||||
|
||||
function setTimeout()
|
||||
{
|
||||
function setTimeout() {
|
||||
let window = Services.appShell.hiddenDOMWindow;
|
||||
return window.setTimeout.apply(window, arguments);
|
||||
}
|
||||
|
||||
function clearTimeout()
|
||||
{
|
||||
function clearTimeout() {
|
||||
let window = Services.appShell.hiddenDOMWindow;
|
||||
return window.clearTimeout.apply(window, arguments);
|
||||
}
|
||||
|
||||
function throttle(func, wait, scope)
|
||||
{
|
||||
function throttle(func, wait, scope) {
|
||||
var timer = null;
|
||||
return function() {
|
||||
if(timer) {
|
||||
@ -2690,8 +2613,7 @@ function throttle(func, wait, scope)
|
||||
* The value from the text editor.
|
||||
* @return {object} an object with 'value' and 'priority' properties.
|
||||
*/
|
||||
function parseCSSValue(aValue)
|
||||
{
|
||||
function parseCSSValue(aValue) {
|
||||
let pieces = aValue.split("!", 2);
|
||||
return {
|
||||
value: pieces[0].trim(),
|
||||
@ -2709,8 +2631,7 @@ function parseCSSValue(aValue)
|
||||
* @return {Array} an array of objects with the following signature:
|
||||
* [{"name": string, "value": string, "priority": string}, ...]
|
||||
*/
|
||||
function parseCSSText(aCssText)
|
||||
{
|
||||
function parseCSSText(aCssText) {
|
||||
let lines = aCssText.match(CSS_LINE_RE);
|
||||
let props = [];
|
||||
|
||||
@ -2739,8 +2660,7 @@ function parseCSSText(aCssText)
|
||||
* Event handler that causes a blur on the target if the input has
|
||||
* multiple CSS properties as the value.
|
||||
*/
|
||||
function blurOnMultipleProperties(e)
|
||||
{
|
||||
function blurOnMultipleProperties(e) {
|
||||
setTimeout(() => {
|
||||
if (parseCSSText(e.target.value).length) {
|
||||
e.target.blur();
|
||||
@ -2751,8 +2671,7 @@ function blurOnMultipleProperties(e)
|
||||
/**
|
||||
* Append a text node to an element.
|
||||
*/
|
||||
function appendText(aParent, aText)
|
||||
{
|
||||
function appendText(aParent, aText) {
|
||||
aParent.appendChild(aParent.ownerDocument.createTextNode(aText));
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,7 @@ function testDivRuleView() {
|
||||
assertTooltipShownOn(ruleView.previewTooltip, uriSpan, () => {
|
||||
let images = panel.getElementsByTagName("image");
|
||||
is(images.length, 1, "Tooltip contains an image");
|
||||
ok(images[0].src === "chrome://global/skin/icons/warning-64.png");
|
||||
ok(images[0].src.startsWith("data:"), "Tooltip contains a data-uri image as expected");
|
||||
|
||||
ruleView.previewTooltip.hide();
|
||||
|
||||
@ -147,7 +147,7 @@ function testComputedView() {
|
||||
assertTooltipShownOn(computedView.tooltip, uriSpan, () => {
|
||||
let images = panel.getElementsByTagName("image");
|
||||
is(images.length, 1, "Tooltip contains an image");
|
||||
ok(images[0].src === "chrome://global/skin/icons/warning-64.png");
|
||||
ok(images[0].src.startsWith("data:"), "Tooltip contains a data-uri in the computed-view too");
|
||||
|
||||
computedView.tooltip.hide();
|
||||
|
||||
|
@ -16,12 +16,28 @@ const URL_CLASS = "url-class";
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
function countAll(fragment) {
|
||||
return fragment.querySelectorAll("*").length;
|
||||
}
|
||||
function countColors(fragment) {
|
||||
return fragment.querySelectorAll("." + COLOR_CLASS).length;
|
||||
}
|
||||
function countUrls(fragment) {
|
||||
return fragment.querySelectorAll("." + URL_CLASS).length;
|
||||
}
|
||||
function getColor(fragment, index) {
|
||||
return fragment.querySelectorAll("." + COLOR_CLASS)[index||0].textContent;
|
||||
}
|
||||
function getUrl(fragment, index) {
|
||||
return fragment.querySelectorAll("." + URL_CLASS)[index||0].textContent;
|
||||
}
|
||||
|
||||
let testData = [
|
||||
{
|
||||
name: "width",
|
||||
value: "100%",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 0);
|
||||
is(countAll(fragment), 0);
|
||||
is(fragment.textContent, "100%");
|
||||
}
|
||||
},
|
||||
@ -29,36 +45,36 @@ function test() {
|
||||
name: "width",
|
||||
value: "blue",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 0);
|
||||
is(countAll(fragment), 0);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "content",
|
||||
value: "'red url(test.png) repeat top left'",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 0);
|
||||
is(countAll(fragment), 0);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "content",
|
||||
value: "\"blue\"",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 0);
|
||||
is(countAll(fragment), 0);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "margin-left",
|
||||
value: "url(something.jpg)",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 0);
|
||||
is(countAll(fragment), 0);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background-color",
|
||||
value: "transparent",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 1);
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(countAll(fragment), 1);
|
||||
is(countColors(fragment), 1);
|
||||
is(fragment.textContent, "transparent");
|
||||
}
|
||||
},
|
||||
@ -66,7 +82,7 @@ function test() {
|
||||
name: "color",
|
||||
value: "red",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(countColors(fragment), 1);
|
||||
is(fragment.textContent, "red");
|
||||
}
|
||||
},
|
||||
@ -74,7 +90,7 @@ function test() {
|
||||
name: "color",
|
||||
value: "#F06",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(countColors(fragment), 1);
|
||||
is(fragment.textContent, "#F06");
|
||||
}
|
||||
},
|
||||
@ -82,8 +98,8 @@ function test() {
|
||||
name: "border-top-left-color",
|
||||
value: "rgba(14, 255, 20, .5)",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 1);
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(countAll(fragment), 1);
|
||||
is(countColors(fragment), 1);
|
||||
is(fragment.textContent, "rgba(14, 255, 20, .5)");
|
||||
}
|
||||
},
|
||||
@ -91,16 +107,16 @@ function test() {
|
||||
name: "border",
|
||||
value: "80em dotted pink",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 1);
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(fragment.querySelector("." + COLOR_CLASS).textContent, "pink");
|
||||
is(countAll(fragment), 1);
|
||||
is(countColors(fragment), 1);
|
||||
is(getColor(fragment), "pink");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "color",
|
||||
value: "red !important",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(countColors(fragment), 1);
|
||||
is(fragment.textContent, "red !important");
|
||||
}
|
||||
},
|
||||
@ -108,38 +124,38 @@ function test() {
|
||||
name: "background",
|
||||
value: "red url(test.png) repeat top left",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(fragment.querySelectorAll("." + URL_CLASS).length, 1);
|
||||
is(fragment.querySelector("." + COLOR_CLASS).textContent, "red");
|
||||
is(fragment.querySelector("." + URL_CLASS).textContent, "test.png");
|
||||
is(fragment.querySelectorAll("*").length, 2);
|
||||
is(countColors(fragment), 1);
|
||||
is(countUrls(fragment), 1);
|
||||
is(getColor(fragment), "red");
|
||||
is(getUrl(fragment), "test.png");
|
||||
is(countAll(fragment), 2);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background",
|
||||
value: "blue url(test.png) repeat top left !important",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(fragment.querySelectorAll("." + URL_CLASS).length, 1);
|
||||
is(fragment.querySelector("." + COLOR_CLASS).textContent, "blue");
|
||||
is(fragment.querySelector("." + URL_CLASS).textContent, "test.png");
|
||||
is(fragment.querySelectorAll("*").length, 2);
|
||||
is(countColors(fragment), 1);
|
||||
is(countUrls(fragment), 1);
|
||||
is(getColor(fragment), "blue");
|
||||
is(getUrl(fragment), "test.png");
|
||||
is(countAll(fragment), 2);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "list-style-image",
|
||||
value: "url(\"images/arrow.gif\")",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 1);
|
||||
is(fragment.querySelector("." + URL_CLASS).textContent, "images/arrow.gif");
|
||||
is(countAll(fragment), 1);
|
||||
is(getUrl(fragment), "images/arrow.gif");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "list-style-image",
|
||||
value: "url(\"images/arrow.gif\")!important",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 1);
|
||||
is(fragment.querySelector("." + URL_CLASS).textContent, "images/arrow.gif");
|
||||
is(countAll(fragment), 1);
|
||||
is(getUrl(fragment), "images/arrow.gif");
|
||||
is(fragment.textContent, "url('images/arrow.gif')!important");
|
||||
}
|
||||
},
|
||||
@ -147,16 +163,16 @@ function test() {
|
||||
name: "-moz-binding",
|
||||
value: "url(http://somesite.com/path/to/binding.xml#someid)",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 1);
|
||||
is(fragment.querySelectorAll("." + URL_CLASS).length, 1);
|
||||
is(fragment.querySelector("." + URL_CLASS).textContent, "http://somesite.com/path/to/binding.xml#someid");
|
||||
is(countAll(fragment), 1);
|
||||
is(countUrls(fragment), 1);
|
||||
is(getUrl(fragment), "http://somesite.com/path/to/binding.xml#someid");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background",
|
||||
value: "linear-gradient(to right, rgba(183,222,237,1) 0%, rgba(33,180,226,1) 30%, rgba(31,170,217,.5) 44%, #F06 75%, red 100%)",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 5);
|
||||
is(countAll(fragment), 5);
|
||||
let allSwatches = fragment.querySelectorAll("." + COLOR_CLASS);
|
||||
is(allSwatches.length, 5);
|
||||
is(allSwatches[0].textContent, "rgba(183,222,237,1)");
|
||||
@ -170,12 +186,54 @@ function test() {
|
||||
name: "background",
|
||||
value: "-moz-radial-gradient(center 45deg, circle closest-side, orange 0%, red 100%)",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 2);
|
||||
is(countAll(fragment), 2);
|
||||
let allSwatches = fragment.querySelectorAll("." + COLOR_CLASS);
|
||||
is(allSwatches.length, 2);
|
||||
is(allSwatches[0].textContent, "orange");
|
||||
is(allSwatches[1].textContent, "red");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background",
|
||||
value: "white url(http://test.com/wow_such_image.png) no-repeat top left",
|
||||
test: fragment => {
|
||||
is(countAll(fragment), 2);
|
||||
is(countUrls(fragment), 1);
|
||||
is(countColors(fragment), 1);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background",
|
||||
value: "url(\"http://test.com/wow_such_(oh-noes)image.png?testid=1&color=red#w00t\")",
|
||||
test: fragment => {
|
||||
is(countAll(fragment), 1);
|
||||
is(getUrl(fragment), "http://test.com/wow_such_(oh-noes)image.png?testid=1&color=red#w00t");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background-image",
|
||||
value: "url(this-is-an-incredible-image.jpeg)",
|
||||
test: fragment => {
|
||||
is(countAll(fragment), 1);
|
||||
is(getUrl(fragment), "this-is-an-incredible-image.jpeg");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background",
|
||||
value: "red url( \"http://wow.com/cool/../../../you're(doingit)wrong\" ) repeat center",
|
||||
test: fragment => {
|
||||
is(countAll(fragment), 2);
|
||||
is(countColors(fragment), 1);
|
||||
is(getUrl(fragment), "http://wow.com/cool/../../../you're(doingit)wrong");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background-image",
|
||||
value: "url(../../../look/at/this/folder/structure/../../red.blue.green.svg )",
|
||||
test: fragment => {
|
||||
is(countAll(fragment), 1);
|
||||
is(getUrl(fragment), "../../../look/at/this/folder/structure/../../red.blue.green.svg");
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -106,6 +106,8 @@ var BrowserUI = {
|
||||
window.addEventListener("MozPrecisePointer", this, true);
|
||||
window.addEventListener("MozImprecisePointer", this, true);
|
||||
|
||||
window.addEventListener("AppCommand", this, true);
|
||||
|
||||
Services.prefs.addObserver("browser.cache.disk_cache_ssl", this, false);
|
||||
|
||||
// Init core UI modules
|
||||
@ -781,6 +783,9 @@ var BrowserUI = {
|
||||
case "MozImprecisePointer":
|
||||
this._onImpreciseInput();
|
||||
break;
|
||||
case "AppCommand":
|
||||
this.handleAppCommandEvent(aEvent);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@ -1140,6 +1145,48 @@ var BrowserUI = {
|
||||
}
|
||||
},
|
||||
|
||||
handleAppCommandEvent: function (aEvent) {
|
||||
switch (aEvent.command) {
|
||||
case "Back":
|
||||
this.doCommand("cmd_back");
|
||||
break;
|
||||
case "Forward":
|
||||
this.doCommand("cmd_forward");
|
||||
break;
|
||||
case "Reload":
|
||||
this.doCommand("cmd_reload");
|
||||
break;
|
||||
case "Stop":
|
||||
this.doCommand("cmd_stop");
|
||||
break;
|
||||
case "Home":
|
||||
this.doCommand("cmd_home");
|
||||
break;
|
||||
case "New":
|
||||
this.doCommand("cmd_newTab");
|
||||
break;
|
||||
case "Close":
|
||||
this.doCommand("cmd_closeTab");
|
||||
break;
|
||||
case "Find":
|
||||
FindHelperUI.show();
|
||||
break;
|
||||
case "Open":
|
||||
this.doCommand("cmd_openFile");
|
||||
break;
|
||||
case "Save":
|
||||
this.doCommand("cmd_savePage");
|
||||
break;
|
||||
case "Search":
|
||||
this.doCommand("cmd_openLocation");
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
aEvent.stopPropagation();
|
||||
aEvent.preventDefault();
|
||||
},
|
||||
|
||||
confirmSanitizeDialog: function () {
|
||||
let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
||||
let title = bundle.GetStringFromName("clearPrivateData.title2");
|
||||
|
@ -323,6 +323,7 @@ this.UITour = {
|
||||
|
||||
teardownTour: function(aWindow, aWindowClosing = false) {
|
||||
aWindow.gBrowser.tabContainer.removeEventListener("TabSelect", this);
|
||||
aWindow.PanelUI.panel.removeEventListener("popuphiding", this.onAppMenuHiding);
|
||||
aWindow.removeEventListener("SSWindowClosing", this);
|
||||
|
||||
let originTabs = this.originTabs.get(aWindow);
|
||||
@ -432,7 +433,10 @@ this.UITour = {
|
||||
}
|
||||
|
||||
if (aTargetName == "pinnedTab") {
|
||||
deferred.resolve({node: this.ensurePinnedTab(aWindow, aSticky)});
|
||||
deferred.resolve({
|
||||
targetName: aTargetName,
|
||||
node: this.ensurePinnedTab(aWindow, aSticky)
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
@ -446,6 +450,7 @@ this.UITour = {
|
||||
aWindow.PanelUI.ensureReady().then(() => {
|
||||
if (typeof targetQuery == "function") {
|
||||
deferred.resolve({
|
||||
targetName: aTargetName,
|
||||
node: targetQuery(aWindow.document),
|
||||
widgetName: targetObject.widgetName,
|
||||
});
|
||||
@ -453,6 +458,7 @@ this.UITour = {
|
||||
}
|
||||
|
||||
deferred.resolve({
|
||||
targetName: aTargetName,
|
||||
node: aWindow.document.querySelector(targetQuery),
|
||||
widgetName: targetObject.widgetName,
|
||||
});
|
||||
@ -573,12 +579,26 @@ this.UITour = {
|
||||
effect = this.highlightEffects[randomEffect];
|
||||
}
|
||||
highlighter.setAttribute("active", effect);
|
||||
highlighter.parentElement.setAttribute("targetName", aTarget.targetName);
|
||||
highlighter.parentElement.hidden = false;
|
||||
|
||||
let targetRect = aTargetEl.getBoundingClientRect();
|
||||
let highlightHeight = targetRect.height;
|
||||
let highlightWidth = targetRect.width;
|
||||
let minDimension = Math.min(highlightHeight, highlightWidth);
|
||||
let maxDimension = Math.max(highlightHeight, highlightWidth);
|
||||
|
||||
highlighter.style.height = targetRect.height + "px";
|
||||
highlighter.style.width = targetRect.width + "px";
|
||||
// If the dimensions are within 40% of eachother, make the highlight a circle with the
|
||||
// largest dimension as the diameter.
|
||||
if (maxDimension / minDimension <= 1.4) {
|
||||
highlightHeight = highlightWidth = maxDimension;
|
||||
highlighter.style.borderRadius = "100%";
|
||||
} else {
|
||||
highlighter.style.borderRadius = "";
|
||||
}
|
||||
|
||||
highlighter.style.height = highlightHeight + "px";
|
||||
highlighter.style.width = highlightWidth + "px";
|
||||
|
||||
// Close a previous highlight so we can relocate the panel.
|
||||
if (highlighter.parentElement.state == "open") {
|
||||
@ -591,10 +611,12 @@ this.UITour = {
|
||||
let paddingTopPx = 0 - parseFloat(containerStyle.paddingTop);
|
||||
let paddingLeftPx = 0 - parseFloat(containerStyle.paddingLeft);
|
||||
let highlightStyle = highlightWindow.getComputedStyle(highlighter);
|
||||
let highlightHeightWithMin = Math.max(highlightHeight, parseFloat(highlightStyle.minHeight));
|
||||
let highlightWidthWithMin = Math.max(highlightWidth, parseFloat(highlightStyle.minWidth));
|
||||
let offsetX = paddingTopPx
|
||||
- (Math.max(0, parseFloat(highlightStyle.minWidth) - targetRect.width) / 2);
|
||||
- (Math.max(0, highlightWidthWithMin - targetRect.width) / 2);
|
||||
let offsetY = paddingLeftPx
|
||||
- (Math.max(0, parseFloat(highlightStyle.minHeight) - targetRect.height) / 2);
|
||||
- (Math.max(0, highlightHeightWithMin - targetRect.height) / 2);
|
||||
highlighter.parentElement.openPopup(aTargetEl, "overlap", offsetX, offsetY);
|
||||
}
|
||||
|
||||
@ -665,6 +687,7 @@ this.UITour = {
|
||||
let tooltipClose = document.getElementById("UITourTooltipClose");
|
||||
tooltipClose.addEventListener("command", this);
|
||||
|
||||
tooltip.setAttribute("targetName", aAnchor.targetName);
|
||||
tooltip.hidden = false;
|
||||
let alignment = "bottomcenter topright";
|
||||
tooltip.openPopup(aAnchorEl, alignment);
|
||||
@ -709,6 +732,7 @@ this.UITour = {
|
||||
|
||||
if (aMenuName == "appMenu") {
|
||||
aWindow.PanelUI.panel.setAttribute("noautohide", "true");
|
||||
aWindow.PanelUI.panel.addEventListener("popuphiding", this.onAppMenuHiding);
|
||||
if (aOpenCallback) {
|
||||
aWindow.PanelUI.panel.addEventListener("popupshown", onPopupShown);
|
||||
}
|
||||
@ -733,6 +757,31 @@ this.UITour = {
|
||||
}
|
||||
},
|
||||
|
||||
onAppMenuHiding: function(aEvent) {
|
||||
let win = aEvent.target.ownerDocument.defaultView;
|
||||
let annotationElements = new Map([
|
||||
// [annotationElement (panel), method to hide the annotation]
|
||||
[win.document.getElementById("UITourHighlightContainer"), UITour.hideHighlight.bind(UITour)],
|
||||
[win.document.getElementById("UITourTooltip"), UITour.hideInfo.bind(UITour)],
|
||||
]);
|
||||
annotationElements.forEach((hideMethod, annotationElement) => {
|
||||
if (annotationElement.state != "closed") {
|
||||
let targetName = annotationElement.getAttribute("targetName");
|
||||
UITour.getTarget(win, targetName).then((aTarget) => {
|
||||
// Since getTarget is async, we need to make sure that the target hasn't
|
||||
// changed since it may have just moved to somewhere outside of the app menu.
|
||||
if (annotationElement.getAttribute("targetName") != aTarget.targetName ||
|
||||
annotationElement.state == "closed" ||
|
||||
!UITour.targetIsInAppMenu(aTarget)) {
|
||||
return;
|
||||
}
|
||||
hideMethod(win);
|
||||
}).then(null, Cu.reportError);
|
||||
}
|
||||
});
|
||||
UITour.appMenuOpenForAnnotation.clear();
|
||||
},
|
||||
|
||||
startUrlbarCapture: function(aWindow, aExpectedText, aUrl) {
|
||||
let urlbar = aWindow.document.getElementById("urlbar");
|
||||
this.urlbarCapture.set(aWindow, {
|
||||
|
@ -10,6 +10,7 @@ support-files =
|
||||
skip-if = os == "linux" # Intermittent failures, bug 951965
|
||||
[browser_UITour2.js]
|
||||
[browser_UITour3.js]
|
||||
[browser_UITour_panel_close_annotation.js]
|
||||
[browser_UITour_sync.js]
|
||||
[browser_taskbar_preview.js]
|
||||
run-if = os == "win"
|
||||
|
@ -9,79 +9,13 @@ let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function loadTestPage(callback, host = "https://example.com/") {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
|
||||
let url = getRootDirectory(gTestPath) + "uitour.html";
|
||||
url = url.replace("chrome://mochitests/content/", host);
|
||||
|
||||
gTestTab = gBrowser.addTab(url);
|
||||
gBrowser.selectedTab = gTestTab;
|
||||
|
||||
gTestTab.linkedBrowser.addEventListener("load", function onLoad() {
|
||||
gTestTab.linkedBrowser.removeEventListener("load", onLoad);
|
||||
|
||||
gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
|
||||
gContentAPI = gContentWindow.Mozilla.UITour;
|
||||
|
||||
waitForFocus(callback, gContentWindow);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function test() {
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", true);
|
||||
let testUri = Services.io.newURI("http://example.com", null, null);
|
||||
Services.perms.add(testUri, "uitour", Services.perms.ALLOW_ACTION);
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
delete window.UITour;
|
||||
delete window.gContentWindow;
|
||||
delete window.gContentAPI;
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
delete window.gTestTab;
|
||||
Services.prefs.clearUserPref("browser.uitour.enabled", true);
|
||||
Services.perms.remove("example.com", "uitour");
|
||||
});
|
||||
|
||||
function done() {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
gTestTab = null;
|
||||
|
||||
let highlight = document.getElementById("UITourHighlightContainer");
|
||||
is_element_hidden(highlight, "Highlight should be closed/hidden after UITour tab is closed");
|
||||
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
is_element_hidden(tooltip, "Tooltip should be closed/hidden after UITour tab is closed");
|
||||
|
||||
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up");
|
||||
|
||||
is(UITour.pinnedTabs.get(window), null, "Any pinned tab should be closed after UITour tab is closed");
|
||||
|
||||
executeSoon(nextTest);
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
if (tests.length == 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
let test = tests.shift();
|
||||
info("Starting " + test.name);
|
||||
loadTestPage(function() {
|
||||
test(done);
|
||||
});
|
||||
}
|
||||
nextTest();
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
let tests = [
|
||||
function test_untrusted_host(done) {
|
||||
loadTestPage(function() {
|
||||
loadUITourTestPage(function() {
|
||||
let bookmarksMenu = document.getElementById("bookmarks-menu-button");
|
||||
ise(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
|
||||
|
||||
@ -92,7 +26,7 @@ let tests = [
|
||||
}, "http://mochi.test:8888/");
|
||||
},
|
||||
function test_unsecure_host(done) {
|
||||
loadTestPage(function() {
|
||||
loadUITourTestPage(function() {
|
||||
let bookmarksMenu = document.getElementById("bookmarks-menu-button");
|
||||
ise(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
|
||||
|
||||
@ -104,7 +38,7 @@ let tests = [
|
||||
},
|
||||
function test_unsecure_host_override(done) {
|
||||
Services.prefs.setBoolPref("browser.uitour.requireSecure", false);
|
||||
loadTestPage(function() {
|
||||
loadUITourTestPage(function() {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
@ -147,6 +81,27 @@ let tests = [
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
waitForElementToBeVisible(highlight, test_highlight_2, "Highlight should be shown after showHighlight()");
|
||||
},
|
||||
function test_highlight_circle(done) {
|
||||
function check_highlight_size() {
|
||||
let panel = highlight.parentElement;
|
||||
let anchor = panel.anchorNode;
|
||||
let anchorRect = anchor.getBoundingClientRect();
|
||||
info("addons target: width: " + anchorRect.width + " height: " + anchorRect.height);
|
||||
let maxDimension = Math.round(Math.max(anchorRect.width, anchorRect.height));
|
||||
let highlightRect = highlight.getBoundingClientRect();
|
||||
info("highlight: width: " + highlightRect.width + " height: " + highlightRect.height);
|
||||
is(Math.round(highlightRect.width), maxDimension, "The width of the highlight should be equal to the largest dimension of the target");
|
||||
is(Math.round(highlightRect.height), maxDimension, "The height of the highlight should be equal to the largest dimension of the target");
|
||||
is(Math.round(highlightRect.height), Math.round(highlightRect.width), "The height and width of the highlight should be the same to create a circle");
|
||||
is(highlight.style.borderRadius, "100%", "The border-radius should be 100% to create a circle");
|
||||
done();
|
||||
}
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
gContentAPI.showHighlight("addons");
|
||||
waitForElementToBeVisible(highlight, check_highlight_size, "Highlight should be shown after showHighlight()");
|
||||
},
|
||||
function test_highlight_customize_auto_open_close(done) {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
gContentAPI.showHighlight("customize");
|
||||
|
@ -9,74 +9,8 @@ let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function loadTestPage(callback, host = "https://example.com/") {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
|
||||
let url = getRootDirectory(gTestPath) + "uitour.html";
|
||||
url = url.replace("chrome://mochitests/content/", host);
|
||||
|
||||
gTestTab = gBrowser.addTab(url);
|
||||
gBrowser.selectedTab = gTestTab;
|
||||
|
||||
gTestTab.linkedBrowser.addEventListener("load", function onLoad() {
|
||||
gTestTab.linkedBrowser.removeEventListener("load", onLoad);
|
||||
|
||||
gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
|
||||
gContentAPI = gContentWindow.Mozilla.UITour;
|
||||
|
||||
waitForFocus(callback, gContentWindow);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function test() {
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", true);
|
||||
let testUri = Services.io.newURI("http://example.com", null, null);
|
||||
Services.perms.add(testUri, "uitour", Services.perms.ALLOW_ACTION);
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
delete window.UITour;
|
||||
delete window.gContentWindow;
|
||||
delete window.gContentAPI;
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
delete window.gTestTab;
|
||||
Services.prefs.clearUserPref("browser.uitour.enabled", true);
|
||||
Services.perms.remove("example.com", "uitour");
|
||||
});
|
||||
|
||||
function done() {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
gTestTab = null;
|
||||
|
||||
let highlight = document.getElementById("UITourHighlightContainer");
|
||||
is_element_hidden(highlight, "Highlight should be closed/hidden after UITour tab is closed");
|
||||
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
is_element_hidden(tooltip, "Tooltip should be closed/hidden after UITour tab is closed");
|
||||
|
||||
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up");
|
||||
|
||||
is(UITour.pinnedTabs.get(window), null, "Any pinned tab should be closed after UITour tab is closed");
|
||||
|
||||
executeSoon(nextTest);
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
if (tests.length == 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
let test = tests.shift();
|
||||
info("Starting " + test.name);
|
||||
loadTestPage(function() {
|
||||
test(done);
|
||||
});
|
||||
}
|
||||
nextTest();
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
let tests = [
|
||||
|
@ -9,74 +9,8 @@ let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function loadTestPage(callback, host = "https://example.com/") {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
|
||||
let url = getRootDirectory(gTestPath) + "uitour.html";
|
||||
url = url.replace("chrome://mochitests/content/", host);
|
||||
|
||||
gTestTab = gBrowser.addTab(url);
|
||||
gBrowser.selectedTab = gTestTab;
|
||||
|
||||
gTestTab.linkedBrowser.addEventListener("load", function onLoad() {
|
||||
gTestTab.linkedBrowser.removeEventListener("load", onLoad);
|
||||
|
||||
gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
|
||||
gContentAPI = gContentWindow.Mozilla.UITour;
|
||||
|
||||
waitForFocus(callback, gContentWindow);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function test() {
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", true);
|
||||
let testUri = Services.io.newURI("http://example.com", null, null);
|
||||
Services.perms.add(testUri, "uitour", Services.perms.ALLOW_ACTION);
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
delete window.UITour;
|
||||
delete window.gContentWindow;
|
||||
delete window.gContentAPI;
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
delete window.gTestTab;
|
||||
Services.prefs.clearUserPref("browser.uitour.enabled", true);
|
||||
Services.perms.remove("example.com", "uitour");
|
||||
});
|
||||
|
||||
function done() {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
gTestTab = null;
|
||||
|
||||
let highlight = document.getElementById("UITourHighlightContainer");
|
||||
is_element_hidden(highlight, "Highlight should be closed/hidden after UITour tab is closed");
|
||||
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
is_element_hidden(tooltip, "Tooltip should be closed/hidden after UITour tab is closed");
|
||||
|
||||
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up");
|
||||
|
||||
is(UITour.pinnedTabs.get(window), null, "Any pinned tab should be closed after UITour tab is closed");
|
||||
|
||||
executeSoon(nextTest);
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
if (tests.length == 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
let test = tests.shift();
|
||||
info("Starting " + test.name);
|
||||
loadTestPage(function() {
|
||||
test(done);
|
||||
});
|
||||
}
|
||||
nextTest();
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
let tests = [
|
||||
|
117
browser/modules/test/browser_UITour_panel_close_annotation.js
Normal file
117
browser/modules/test/browser_UITour_panel_close_annotation.js
Normal file
@ -0,0 +1,117 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that annotations disappear when their target is hidden.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
registerCleanupFunction(() => {
|
||||
// Close the find bar in case it's open in the remaining tab
|
||||
gBrowser.getFindBar(gBrowser.selectedTab).close();
|
||||
});
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
let tests = [
|
||||
function test_highlight_move_outside_panel(done) {
|
||||
gContentAPI.showInfo("urlbar", "test title", "test text");
|
||||
gContentAPI.showHighlight("customize");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Move the highlight outside which should close the app menu.
|
||||
gContentAPI.showHighlight("appMenu");
|
||||
waitForPopupAtAnchor(highlight.parentElement, document.getElementById("PanelUI-menu-button"), () => {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed after the highlight moved elsewhere.");
|
||||
is(tooltip.state, "open", "The info panel should have remained open");
|
||||
done();
|
||||
}, "Highlight should move to the appMenu button and still be visible");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_highlight_panel_hideMenu(done) {
|
||||
gContentAPI.showHighlight("customize");
|
||||
gContentAPI.showInfo("search", "test title", "test text");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Close the app menu and make sure the highlight also disappeared.
|
||||
gContentAPI.hideMenu("appMenu");
|
||||
waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel still should have closed");
|
||||
is(tooltip.state, "open", "The info panel should have remained open");
|
||||
done();
|
||||
}, "Highlight should have disappeared when panel closed");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_highlight_panel_click_find(done) {
|
||||
gContentAPI.showHighlight("help");
|
||||
gContentAPI.showInfo("searchProvider", "test title", "test text");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Click the find button which should close the panel.
|
||||
let findButton = document.getElementById("find-button");
|
||||
EventUtils.synthesizeMouseAtCenter(findButton, {});
|
||||
waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed when the find bar opened");
|
||||
is(tooltip.state, "open", "The info panel should have remained open");
|
||||
done();
|
||||
}, "Highlight should have disappeared when panel closed");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_highlight_info_panel_click_find(done) {
|
||||
gContentAPI.showHighlight("help");
|
||||
gContentAPI.showInfo("customize", "customize me!", "awesome!");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Click the find button which should close the panel.
|
||||
let findButton = document.getElementById("find-button");
|
||||
EventUtils.synthesizeMouseAtCenter(findButton, {});
|
||||
waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed when the find bar opened");
|
||||
waitForElementToBeHidden(tooltip, function checkTooltipIsClosed() {
|
||||
isnot(tooltip.state, "open", "The info panel should have closed too");
|
||||
done();
|
||||
}, "Tooltip should hide with the menu");
|
||||
}, "Highlight should have disappeared when panel closed");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_info_move_outside_panel(done) {
|
||||
gContentAPI.showInfo("addons", "test title", "test text");
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
let addonsButton = document.getElementById("add-ons-button");
|
||||
waitForPopupAtAnchor(tooltip, addonsButton, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Move the info panel outside which should close the app menu.
|
||||
gContentAPI.showInfo("appMenu", "Cool menu button", "It's three lines");
|
||||
waitForPopupAtAnchor(tooltip, document.getElementById("PanelUI-menu-button"), () => {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Menu should have closed after the highlight moved elsewhere.");
|
||||
is(highlight.parentElement.state, "open", "The highlight should have remained visible");
|
||||
done();
|
||||
}, "Tooltip should move to the appMenu button and still be visible");
|
||||
}, "Tooltip should be shown after showInfo() for a panel item");
|
||||
},
|
||||
|
||||
];
|
@ -9,75 +9,11 @@ let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function loadTestPage(callback, host = "https://example.com/") {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
|
||||
let url = getRootDirectory(gTestPath) + "uitour.html";
|
||||
url = url.replace("chrome://mochitests/content/", host);
|
||||
|
||||
gTestTab = gBrowser.addTab(url);
|
||||
gBrowser.selectedTab = gTestTab;
|
||||
|
||||
gTestTab.linkedBrowser.addEventListener("load", function onLoad() {
|
||||
gTestTab.linkedBrowser.removeEventListener("load", onLoad);
|
||||
|
||||
gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
|
||||
gContentAPI = gContentWindow.Mozilla.UITour;
|
||||
|
||||
waitForFocus(callback, gContentWindow);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function test() {
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", true);
|
||||
let testUri = Services.io.newURI("http://example.com", null, null);
|
||||
Services.perms.add(testUri, "uitour", Services.perms.ALLOW_ACTION);
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
delete window.UITour;
|
||||
delete window.gContentWindow;
|
||||
delete window.gContentAPI;
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
delete window.gTestTab;
|
||||
Services.prefs.clearUserPref("browser.uitour.enabled", true);
|
||||
Services.prefs.clearUserPref("services.sync.username");
|
||||
Services.perms.remove("example.com", "uitour");
|
||||
});
|
||||
|
||||
function done() {
|
||||
executeSoon(() => {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
gTestTab = null;
|
||||
|
||||
let highlight = document.getElementById("UITourHighlightContainer");
|
||||
is_element_hidden(highlight, "Highlight should be closed/hidden after UITour tab is closed");
|
||||
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
is_element_hidden(tooltip, "Tooltip should be closed/hidden after UITour tab is closed");
|
||||
|
||||
is(UITour.pinnedTabs.get(window), null, "Any pinned tab should be closed after UITour tab is closed");
|
||||
|
||||
executeSoon(nextTest);
|
||||
});
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
if (tests.length == 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
let test = tests.shift();
|
||||
info("Starting " + test.name);
|
||||
loadTestPage(function() {
|
||||
test(done);
|
||||
});
|
||||
}
|
||||
nextTest();
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
let tests = [
|
||||
|
@ -53,11 +53,20 @@ function waitForElementToBeVisible(element, nextTest, msg) {
|
||||
"Timeout waiting for visibility: " + msg);
|
||||
}
|
||||
|
||||
function waitForElementToBeHidden(element, nextTest, msg) {
|
||||
waitForCondition(() => is_hidden(element),
|
||||
() => {
|
||||
ok(true, msg);
|
||||
nextTest();
|
||||
},
|
||||
"Timeout waiting for invisibility: " + msg);
|
||||
}
|
||||
|
||||
function waitForPopupAtAnchor(popup, anchorNode, nextTest, msg) {
|
||||
waitForCondition(() => popup.popupBoxObject.anchorNode == anchorNode,
|
||||
() => {
|
||||
ok(true, msg);
|
||||
is_element_visible(popup);
|
||||
is_element_visible(popup, "Popup should be visible");
|
||||
nextTest();
|
||||
},
|
||||
"Timeout waiting for popup at anchor: " + msg);
|
||||
@ -67,3 +76,75 @@ function is_element_hidden(element, msg) {
|
||||
isnot(element, null, "Element should not be null, when checking visibility");
|
||||
ok(is_hidden(element), msg);
|
||||
}
|
||||
|
||||
function loadUITourTestPage(callback, host = "https://example.com/") {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
|
||||
let url = getRootDirectory(gTestPath) + "uitour.html";
|
||||
url = url.replace("chrome://mochitests/content/", host);
|
||||
|
||||
gTestTab = gBrowser.addTab(url);
|
||||
gBrowser.selectedTab = gTestTab;
|
||||
|
||||
gTestTab.linkedBrowser.addEventListener("load", function onLoad() {
|
||||
gTestTab.linkedBrowser.removeEventListener("load", onLoad);
|
||||
|
||||
gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
|
||||
gContentAPI = gContentWindow.Mozilla.UITour;
|
||||
|
||||
waitForFocus(callback, gContentWindow);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function UITourTest() {
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", true);
|
||||
let testUri = Services.io.newURI("http://example.com", null, null);
|
||||
Services.perms.add(testUri, "uitour", Services.perms.ALLOW_ACTION);
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
delete window.UITour;
|
||||
delete window.gContentWindow;
|
||||
delete window.gContentAPI;
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
delete window.gTestTab;
|
||||
Services.prefs.clearUserPref("browser.uitour.enabled", true);
|
||||
Services.perms.remove("example.com", "uitour");
|
||||
});
|
||||
|
||||
function done() {
|
||||
executeSoon(() => {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
gTestTab = null;
|
||||
|
||||
let highlight = document.getElementById("UITourHighlightContainer");
|
||||
is_element_hidden(highlight, "Highlight should be closed/hidden after UITour tab is closed");
|
||||
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
is_element_hidden(tooltip, "Tooltip should be closed/hidden after UITour tab is closed");
|
||||
|
||||
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up");
|
||||
|
||||
is(UITour.pinnedTabs.get(window), null, "Any pinned tab should be closed after UITour tab is closed");
|
||||
|
||||
executeSoon(nextTest);
|
||||
});
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
if (tests.length == 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
let test = tests.shift();
|
||||
info("Starting " + test.name);
|
||||
loadUITourTestPage(function() {
|
||||
test(done);
|
||||
});
|
||||
}
|
||||
nextTest();
|
||||
}
|
||||
|
@ -1532,10 +1532,6 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||
-moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar-drag");
|
||||
}
|
||||
|
||||
#TabsToolbar:not(:-moz-lwtheme) > #tabbrowser-tabs > .tabbrowser-tab:not([selected]) {
|
||||
color: -moz-menubartext;
|
||||
}
|
||||
|
||||
.tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-label {
|
||||
outline: 1px dotted;
|
||||
}
|
||||
|
@ -2645,9 +2645,8 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.tabbrowser-tab:not(:-moz-lwtheme) {
|
||||
color: #333;
|
||||
text-shadow: @loweredShadow@;
|
||||
.tabbrowser-tab[selected=true]:not(:-moz-lwtheme) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.tabbrowser-tab[selected=true]:-moz-lwtheme {
|
||||
@ -2694,6 +2693,11 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
#TabsToolbar:not(:-moz-lwtheme) {
|
||||
color: #333;
|
||||
text-shadow: @loweredShadow@;
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw the bottom border of the tabstrip when core doesn't do it for us:
|
||||
*/
|
||||
|
@ -11,6 +11,19 @@
|
||||
|
||||
%include ../browser.inc
|
||||
|
||||
#PanelUI-button {
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.3) 30%, hsla(0,0%,100%,.3) 70%, hsla(0,0%,100%,0)),
|
||||
-moz-linear-gradient(hsla(210,54%,20%,0), hsla(210,54%,20%,.3) 30%, hsla(210,54%,20%,.3) 70%, hsla(210,54%,20%,0)),
|
||||
-moz-linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.3) 30%, hsla(0,0%,100%,.3) 70%, hsla(0,0%,100%,0));
|
||||
background-size: 1px calc(100% - 1px), 1px calc(100% - 1px), 1px calc(100% - 1px) !important;
|
||||
background-position: 0px 0px, 1px 0px, 2px 0px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#PanelUI-menu-button {
|
||||
margin: 0 7px 0 9px;
|
||||
}
|
||||
|
||||
.panel-subviews {
|
||||
padding: 4px;
|
||||
background-color: hsla(0,0%,100%,.97);
|
||||
|
@ -119,6 +119,7 @@
|
||||
width: @tabCurveWidth@;
|
||||
}
|
||||
|
||||
.tabbrowser-tab:not([selected=true]),
|
||||
.tabbrowser-tab:-moz-lwtheme {
|
||||
color: inherit;
|
||||
}
|
||||
|
@ -1577,10 +1577,6 @@ toolbarbutton[type="socialmark"] > .toolbarbutton-icon {
|
||||
-moz-image-region: rect(0, 64px, 16px, 48px) !important;
|
||||
}
|
||||
|
||||
#main-window[tabsintitlebar]:not([inFullscreen]) .tabbrowser-tab:not([selected]):not(:-moz-lwtheme) {
|
||||
color: CaptionText;
|
||||
}
|
||||
|
||||
/* tabbrowser-tab focus ring */
|
||||
.tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-label {
|
||||
outline: 1px dotted;
|
||||
|
@ -19,6 +19,8 @@ import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
|
||||
import org.mozilla.gecko.gfx.LayerMarginsAnimator;
|
||||
import org.mozilla.gecko.health.BrowserHealthRecorder;
|
||||
import org.mozilla.gecko.health.BrowserHealthReporter;
|
||||
import org.mozilla.gecko.health.HealthRecorder;
|
||||
import org.mozilla.gecko.health.SessionInformation;
|
||||
import org.mozilla.gecko.home.BrowserSearch;
|
||||
import org.mozilla.gecko.home.HomePager;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
@ -29,6 +31,7 @@ import org.mozilla.gecko.prompts.Prompt;
|
||||
import org.mozilla.gecko.toolbar.AutocompleteHandler;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar;
|
||||
import org.mozilla.gecko.util.Clipboard;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GamepadUtils;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
@ -43,6 +46,7 @@ import org.json.JSONObject;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
@ -2583,4 +2587,19 @@ abstract public class BrowserApp extends GeckoApp
|
||||
mShowActionModeEndAnimation = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HealthRecorder createHealthRecorder(final Context context,
|
||||
final String profilePath,
|
||||
final EventDispatcher dispatcher,
|
||||
final String osLocale,
|
||||
final String appLocale,
|
||||
final SessionInformation previousSession) {
|
||||
return new BrowserHealthRecorder(context,
|
||||
profilePath,
|
||||
dispatcher,
|
||||
osLocale,
|
||||
appLocale,
|
||||
previousSession);
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,9 @@ import org.mozilla.gecko.prompts.PromptService;
|
||||
import org.mozilla.gecko.menu.GeckoMenu;
|
||||
import org.mozilla.gecko.menu.GeckoMenuInflater;
|
||||
import org.mozilla.gecko.menu.MenuPanel;
|
||||
import org.mozilla.gecko.health.BrowserHealthRecorder;
|
||||
import org.mozilla.gecko.health.BrowserHealthRecorder.SessionInformation;
|
||||
import org.mozilla.gecko.health.HealthRecorder;
|
||||
import org.mozilla.gecko.health.SessionInformation;
|
||||
import org.mozilla.gecko.health.StubbedHealthRecorder;
|
||||
import org.mozilla.gecko.preferences.GeckoPreferences;
|
||||
import org.mozilla.gecko.updater.UpdateService;
|
||||
import org.mozilla.gecko.updater.UpdateServiceHelper;
|
||||
@ -214,7 +215,7 @@ public abstract class GeckoApp
|
||||
|
||||
private String mPrivateBrowsingSession;
|
||||
|
||||
private volatile BrowserHealthRecorder mHealthRecorder = null;
|
||||
private volatile HealthRecorder mHealthRecorder = null;
|
||||
|
||||
private int mSignalStrenth;
|
||||
private PhoneStateListener mPhoneStateListener = null;
|
||||
@ -582,7 +583,7 @@ public abstract class GeckoApp
|
||||
// know that mHealthRecorder will exist. That doesn't stop us being
|
||||
// paranoid.
|
||||
// This method is cheap, so don't spawn a new runnable.
|
||||
final BrowserHealthRecorder rec = mHealthRecorder;
|
||||
final HealthRecorder rec = mHealthRecorder;
|
||||
if (rec != null) {
|
||||
rec.recordGeckoStartupTime(mGeckoReadyStartupTimer.getElapsed());
|
||||
}
|
||||
@ -1303,7 +1304,7 @@ public abstract class GeckoApp
|
||||
// of the activity itself.
|
||||
final String profilePath = getProfile().getDir().getAbsolutePath();
|
||||
final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
|
||||
Log.i(LOGTAG, "Creating BrowserHealthRecorder.");
|
||||
Log.i(LOGTAG, "Creating HealthRecorder.");
|
||||
|
||||
final String osLocale = Locale.getDefault().toString();
|
||||
String appLocale = LocaleManager.getAndApplyPersistedLocale();
|
||||
@ -1313,12 +1314,12 @@ public abstract class GeckoApp
|
||||
appLocale = osLocale;
|
||||
}
|
||||
|
||||
mHealthRecorder = new BrowserHealthRecorder(GeckoApp.this,
|
||||
profilePath,
|
||||
dispatcher,
|
||||
osLocale,
|
||||
appLocale,
|
||||
previousSession);
|
||||
mHealthRecorder = GeckoApp.this.createHealthRecorder(GeckoApp.this,
|
||||
profilePath,
|
||||
dispatcher,
|
||||
osLocale,
|
||||
appLocale,
|
||||
previousSession);
|
||||
|
||||
final String uiLocale = appLocale;
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@ -1599,7 +1600,7 @@ public abstract class GeckoApp
|
||||
ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final BrowserHealthRecorder rec = mHealthRecorder;
|
||||
final HealthRecorder rec = mHealthRecorder;
|
||||
if (rec != null) {
|
||||
rec.recordJavaStartupTime(javaDuration);
|
||||
}
|
||||
@ -1962,7 +1963,7 @@ public abstract class GeckoApp
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Now construct the new session on BrowserHealthRecorder's behalf. We do this here
|
||||
// Now construct the new session on HealthRecorder's behalf. We do this here
|
||||
// so it can benefit from a single near-startup prefs commit.
|
||||
SessionInformation currentSession = new SessionInformation(now, realTime);
|
||||
|
||||
@ -1972,7 +1973,7 @@ public abstract class GeckoApp
|
||||
currentSession.recordBegin(editor);
|
||||
editor.commit();
|
||||
|
||||
final BrowserHealthRecorder rec = mHealthRecorder;
|
||||
final HealthRecorder rec = mHealthRecorder;
|
||||
if (rec != null) {
|
||||
rec.setCurrentSession(currentSession);
|
||||
} else {
|
||||
@ -1995,7 +1996,7 @@ public abstract class GeckoApp
|
||||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
final BrowserHealthRecorder rec = mHealthRecorder;
|
||||
final HealthRecorder rec = mHealthRecorder;
|
||||
final Context context = this;
|
||||
|
||||
// In some way it's sad that Android will trigger StrictMode warnings
|
||||
@ -2114,9 +2115,9 @@ public abstract class GeckoApp
|
||||
SmsManager.getInstance().shutdown();
|
||||
}
|
||||
|
||||
final BrowserHealthRecorder rec = mHealthRecorder;
|
||||
final HealthRecorder rec = mHealthRecorder;
|
||||
mHealthRecorder = null;
|
||||
if (rec != null) {
|
||||
if (rec != null && rec.isEnabled()) {
|
||||
// Closing a BrowserHealthRecorder could incur a write.
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
@ -2773,7 +2774,7 @@ public abstract class GeckoApp
|
||||
|
||||
/**
|
||||
* Use LocaleManager to change our persisted and current locales,
|
||||
* and poke BrowserHealthRecorder to tell it of our changed state.
|
||||
* and poke HealthRecorder to tell it of our changed state.
|
||||
*/
|
||||
private void setLocale(final String locale) {
|
||||
if (locale == null) {
|
||||
@ -2784,15 +2785,17 @@ public abstract class GeckoApp
|
||||
return;
|
||||
}
|
||||
|
||||
final BrowserHealthRecorder rec = mHealthRecorder;
|
||||
if (rec == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean startNewSession = true;
|
||||
final boolean shouldRestart = false;
|
||||
rec.onAppLocaleChanged(resultant);
|
||||
rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED);
|
||||
|
||||
// If the HealthRecorder is not yet initialized (unlikely), the locale change won't
|
||||
// trigger a session transition and subsequent events will be recorded in an environment
|
||||
// with the wrong locale.
|
||||
final HealthRecorder rec = mHealthRecorder;
|
||||
if (rec != null) {
|
||||
rec.onAppLocaleChanged(resultant);
|
||||
rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED);
|
||||
}
|
||||
|
||||
if (!shouldRestart) {
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@ -2827,4 +2830,14 @@ public abstract class GeckoApp
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected HealthRecorder createHealthRecorder(final Context context,
|
||||
final String profilePath,
|
||||
final EventDispatcher dispatcher,
|
||||
final String osLocale,
|
||||
final String appLocale,
|
||||
final SessionInformation previousSession) {
|
||||
// GeckoApp does not need to record any health information - return a stub.
|
||||
return new StubbedHealthRecorder();
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.home.HomeConfigInvalidator;
|
||||
import org.mozilla.gecko.mozglue.GeckoLoader;
|
||||
import org.mozilla.gecko.util.Clipboard;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
@ -68,6 +69,7 @@ public class GeckoApplication extends Application {
|
||||
GeckoBatteryManager.getInstance().start();
|
||||
GeckoNetworkManager.getInstance().init(getApplicationContext());
|
||||
MemoryMonitor.getInstance().init(getApplicationContext());
|
||||
HomeConfigInvalidator.getInstance().init(getApplicationContext());
|
||||
|
||||
mInited = true;
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
*
|
||||
* Shut it down when you're done being a browser: {@link #close()}.
|
||||
*/
|
||||
public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
public class BrowserHealthRecorder implements HealthRecorder, GeckoEventListener {
|
||||
private static final String LOG_TAG = "GeckoHealthRec";
|
||||
private static final String PREF_ACCEPT_LANG = "intl.accept_languages";
|
||||
private static final String PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
|
||||
@ -97,128 +97,10 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
private final ProfileInformationCache profileCache;
|
||||
private final EventDispatcher dispatcher;
|
||||
|
||||
public static class SessionInformation {
|
||||
private static final String LOG_TAG = "GeckoSessInfo";
|
||||
|
||||
public static final String PREFS_SESSION_START = "sessionStart";
|
||||
|
||||
public final long wallStartTime; // System wall clock.
|
||||
public final long realStartTime; // Realtime clock.
|
||||
|
||||
private final boolean wasOOM;
|
||||
private final boolean wasStopped;
|
||||
|
||||
private volatile long timedGeckoStartup = -1;
|
||||
private volatile long timedJavaStartup = -1;
|
||||
|
||||
// Current sessions don't (right now) care about wasOOM/wasStopped.
|
||||
// Eventually we might want to lift that logic out of GeckoApp.
|
||||
public SessionInformation(long wallTime, long realTime) {
|
||||
this(wallTime, realTime, false, false);
|
||||
}
|
||||
|
||||
// Previous sessions do...
|
||||
public SessionInformation(long wallTime, long realTime, boolean wasOOM, boolean wasStopped) {
|
||||
this.wallStartTime = wallTime;
|
||||
this.realStartTime = realTime;
|
||||
this.wasOOM = wasOOM;
|
||||
this.wasStopped = wasStopped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new SessionInformation instance from the supplied prefs object.
|
||||
*
|
||||
* This includes retrieving OOM/crash data, as well as timings.
|
||||
*
|
||||
* If no wallStartTime was found, that implies that the previous
|
||||
* session was correctly recorded, and an object with a zero
|
||||
* wallStartTime is returned.
|
||||
*/
|
||||
public static SessionInformation fromSharedPrefs(SharedPreferences prefs) {
|
||||
boolean wasOOM = prefs.getBoolean(GeckoApp.PREFS_OOM_EXCEPTION, false);
|
||||
boolean wasStopped = prefs.getBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
|
||||
long wallStartTime = prefs.getLong(PREFS_SESSION_START, 0L);
|
||||
long realStartTime = 0L;
|
||||
Log.d(LOG_TAG, "Building SessionInformation from prefs: " +
|
||||
wallStartTime + ", " + realStartTime + ", " +
|
||||
wasStopped + ", " + wasOOM);
|
||||
return new SessionInformation(wallStartTime, realStartTime, wasOOM, wasStopped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new SessionInformation instance to 'split' the current
|
||||
* session.
|
||||
*/
|
||||
public static SessionInformation forRuntimeTransition() {
|
||||
final boolean wasOOM = false;
|
||||
final boolean wasStopped = true;
|
||||
final long wallStartTime = System.currentTimeMillis();
|
||||
final long realStartTime = android.os.SystemClock.elapsedRealtime();
|
||||
Log.v(LOG_TAG, "Recording runtime session transition: " +
|
||||
wallStartTime + ", " + realStartTime);
|
||||
return new SessionInformation(wallStartTime, realStartTime, wasOOM, wasStopped);
|
||||
}
|
||||
|
||||
public boolean wasKilled() {
|
||||
return wasOOM || !wasStopped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the beginning of this session to SharedPreferences by
|
||||
* recording our start time. If a session was already recorded, it is
|
||||
* overwritten (there can only be one running session at a time). Does
|
||||
* not commit the editor.
|
||||
*/
|
||||
public void recordBegin(SharedPreferences.Editor editor) {
|
||||
Log.d(LOG_TAG, "Recording start of session: " + this.wallStartTime);
|
||||
editor.putLong(PREFS_SESSION_START, this.wallStartTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the completion of this session to SharedPreferences by
|
||||
* deleting our start time. Does not commit the editor.
|
||||
*/
|
||||
public void recordCompletion(SharedPreferences.Editor editor) {
|
||||
Log.d(LOG_TAG, "Recording session done: " + this.wallStartTime);
|
||||
editor.remove(PREFS_SESSION_START);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JSON that we'll put in the DB for this session.
|
||||
*/
|
||||
public JSONObject getCompletionJSON(String reason, long realEndTime) throws JSONException {
|
||||
long durationSecs = (realEndTime - this.realStartTime) / 1000;
|
||||
JSONObject out = new JSONObject();
|
||||
out.put("r", reason);
|
||||
out.put("d", durationSecs);
|
||||
if (this.timedGeckoStartup > 0) {
|
||||
out.put("sg", this.timedGeckoStartup);
|
||||
}
|
||||
if (this.timedJavaStartup > 0) {
|
||||
out.put("sj", this.timedJavaStartup);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public JSONObject getCrashedJSON() throws JSONException {
|
||||
JSONObject out = new JSONObject();
|
||||
// We use ints here instead of booleans, because we're packing
|
||||
// stuff into JSON, and saving bytes in the DB is a worthwhile
|
||||
// goal.
|
||||
out.put("oom", this.wasOOM ? 1 : 0);
|
||||
out.put("stopped", this.wasStopped ? 1 : 0);
|
||||
out.put("r", "A");
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
// We track previousSession to avoid order-of-initialization confusion. We
|
||||
// accept it in the constructor, and process it after init.
|
||||
private final SessionInformation previousSession;
|
||||
private volatile SessionInformation session = null;
|
||||
public SessionInformation getCurrentSession() {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
public void setCurrentSession(SessionInformation session) {
|
||||
this.session = session;
|
||||
@ -228,13 +110,13 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
if (this.session == null) {
|
||||
return;
|
||||
}
|
||||
this.session.timedGeckoStartup = duration;
|
||||
this.session.setTimedGeckoStartup(duration);
|
||||
}
|
||||
public void recordJavaStartupTime(long duration) {
|
||||
if (this.session == null) {
|
||||
return;
|
||||
}
|
||||
this.session.timedJavaStartup = duration;
|
||||
this.session.setTimedJavaStartup(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -286,6 +168,10 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shut down database connections, unregister event listeners, and perform
|
||||
* provider-specific uninitialization.
|
||||
@ -1017,4 +903,3 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
session.recordCompletion(editor);
|
||||
}
|
||||
}
|
||||
|
||||
|
37
mobile/android/base/health/HealthRecorder.java
Normal file
37
mobile/android/base/health/HealthRecorder.java
Normal file
@ -0,0 +1,37 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.health;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* HealthRecorder is an interface into the Firefox Health Report storage system.
|
||||
*/
|
||||
public interface HealthRecorder {
|
||||
/**
|
||||
* Returns whether the Health Recorder is actively recording events.
|
||||
*/
|
||||
public boolean isEnabled();
|
||||
|
||||
public void setCurrentSession(SessionInformation session);
|
||||
public void checkForOrphanSessions();
|
||||
|
||||
public void recordGeckoStartupTime(long duration);
|
||||
public void recordJavaStartupTime(long duration);
|
||||
public void recordSearch(final String engineID, final String location);
|
||||
public void recordSessionEnd(String reason, SharedPreferences.Editor editor);
|
||||
public void recordSessionEnd(String reason, SharedPreferences.Editor editor, final int environment);
|
||||
|
||||
public void onAppLocaleChanged(String to);
|
||||
public void onAddonChanged(String id, JSONObject json);
|
||||
public void onAddonUninstalling(String id);
|
||||
public void onEnvironmentChanged();
|
||||
public void onEnvironmentChanged(final boolean startNewSession, final String sessionEndReason);
|
||||
|
||||
public void close();
|
||||
}
|
137
mobile/android/base/health/SessionInformation.java
Normal file
137
mobile/android/base/health/SessionInformation.java
Normal file
@ -0,0 +1,137 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.health;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.GeckoApp;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class SessionInformation {
|
||||
private static final String LOG_TAG = "GeckoSessInfo";
|
||||
|
||||
public static final String PREFS_SESSION_START = "sessionStart";
|
||||
|
||||
public final long wallStartTime; // System wall clock.
|
||||
public final long realStartTime; // Realtime clock.
|
||||
|
||||
private final boolean wasOOM;
|
||||
private final boolean wasStopped;
|
||||
|
||||
private volatile long timedGeckoStartup = -1;
|
||||
private volatile long timedJavaStartup = -1;
|
||||
|
||||
// Current sessions don't (right now) care about wasOOM/wasStopped.
|
||||
// Eventually we might want to lift that logic out of GeckoApp.
|
||||
public SessionInformation(long wallTime, long realTime) {
|
||||
this(wallTime, realTime, false, false);
|
||||
}
|
||||
|
||||
// Previous sessions do...
|
||||
public SessionInformation(long wallTime, long realTime, boolean wasOOM, boolean wasStopped) {
|
||||
this.wallStartTime = wallTime;
|
||||
this.realStartTime = realTime;
|
||||
this.wasOOM = wasOOM;
|
||||
this.wasStopped = wasStopped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new SessionInformation instance from the supplied prefs object.
|
||||
*
|
||||
* This includes retrieving OOM/crash data, as well as timings.
|
||||
*
|
||||
* If no wallStartTime was found, that implies that the previous
|
||||
* session was correctly recorded, and an object with a zero
|
||||
* wallStartTime is returned.
|
||||
*/
|
||||
public static SessionInformation fromSharedPrefs(SharedPreferences prefs) {
|
||||
boolean wasOOM = prefs.getBoolean(GeckoApp.PREFS_OOM_EXCEPTION, false);
|
||||
boolean wasStopped = prefs.getBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
|
||||
long wallStartTime = prefs.getLong(PREFS_SESSION_START, 0L);
|
||||
long realStartTime = 0L;
|
||||
Log.d(LOG_TAG, "Building SessionInformation from prefs: " +
|
||||
wallStartTime + ", " + realStartTime + ", " +
|
||||
wasStopped + ", " + wasOOM);
|
||||
return new SessionInformation(wallStartTime, realStartTime, wasOOM, wasStopped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new SessionInformation instance to 'split' the current
|
||||
* session.
|
||||
*/
|
||||
public static SessionInformation forRuntimeTransition() {
|
||||
final boolean wasOOM = false;
|
||||
final boolean wasStopped = true;
|
||||
final long wallStartTime = System.currentTimeMillis();
|
||||
final long realStartTime = android.os.SystemClock.elapsedRealtime();
|
||||
Log.v(LOG_TAG, "Recording runtime session transition: " +
|
||||
wallStartTime + ", " + realStartTime);
|
||||
return new SessionInformation(wallStartTime, realStartTime, wasOOM, wasStopped);
|
||||
}
|
||||
|
||||
public boolean wasKilled() {
|
||||
return wasOOM || !wasStopped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the beginning of this session to SharedPreferences by
|
||||
* recording our start time. If a session was already recorded, it is
|
||||
* overwritten (there can only be one running session at a time). Does
|
||||
* not commit the editor.
|
||||
*/
|
||||
public void recordBegin(SharedPreferences.Editor editor) {
|
||||
Log.d(LOG_TAG, "Recording start of session: " + this.wallStartTime);
|
||||
editor.putLong(PREFS_SESSION_START, this.wallStartTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the completion of this session to SharedPreferences by
|
||||
* deleting our start time. Does not commit the editor.
|
||||
*/
|
||||
public void recordCompletion(SharedPreferences.Editor editor) {
|
||||
Log.d(LOG_TAG, "Recording session done: " + this.wallStartTime);
|
||||
editor.remove(PREFS_SESSION_START);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JSON that we'll put in the DB for this session.
|
||||
*/
|
||||
public JSONObject getCompletionJSON(String reason, long realEndTime) throws JSONException {
|
||||
long durationSecs = (realEndTime - this.realStartTime) / 1000;
|
||||
JSONObject out = new JSONObject();
|
||||
out.put("r", reason);
|
||||
out.put("d", durationSecs);
|
||||
if (this.timedGeckoStartup > 0) {
|
||||
out.put("sg", this.timedGeckoStartup);
|
||||
}
|
||||
if (this.timedJavaStartup > 0) {
|
||||
out.put("sj", this.timedJavaStartup);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public JSONObject getCrashedJSON() throws JSONException {
|
||||
JSONObject out = new JSONObject();
|
||||
// We use ints here instead of booleans, because we're packing
|
||||
// stuff into JSON, and saving bytes in the DB is a worthwhile
|
||||
// goal.
|
||||
out.put("oom", this.wasOOM ? 1 : 0);
|
||||
out.put("stopped", this.wasStopped ? 1 : 0);
|
||||
out.put("r", "A");
|
||||
return out;
|
||||
}
|
||||
|
||||
public void setTimedGeckoStartup(final long duration) {
|
||||
timedGeckoStartup = duration;
|
||||
}
|
||||
|
||||
public void setTimedJavaStartup(final long duration) {
|
||||
timedJavaStartup = duration;
|
||||
}
|
||||
}
|
35
mobile/android/base/health/StubbedHealthRecorder.java
Normal file
35
mobile/android/base/health/StubbedHealthRecorder.java
Normal file
@ -0,0 +1,35 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.health;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* StubbedHealthRecorder is an implementation of HealthRecorder that does (you guessed it!)
|
||||
* nothing.
|
||||
*/
|
||||
public class StubbedHealthRecorder implements HealthRecorder {
|
||||
public boolean isEnabled() { return false; }
|
||||
|
||||
public void setCurrentSession(SessionInformation session) { }
|
||||
public void checkForOrphanSessions() { }
|
||||
|
||||
public void recordGeckoStartupTime(long duration) { }
|
||||
public void recordJavaStartupTime(long duration) { }
|
||||
public void recordSearch(final String engineID, final String location) { }
|
||||
public void recordSessionEnd(String reason, SharedPreferences.Editor editor) { }
|
||||
public void recordSessionEnd(String reason, SharedPreferences.Editor editor, final int environment) { }
|
||||
|
||||
public void onAppLocaleChanged(String to) { }
|
||||
public void onAddonChanged(String id, JSONObject json) { }
|
||||
public void onAddonUninstalling(String id) { }
|
||||
public void onEnvironmentChanged() { }
|
||||
public void onEnvironmentChanged(final boolean startNewSession, final String sessionEndReason) { }
|
||||
|
||||
public void close() { }
|
||||
}
|
@ -533,9 +533,9 @@ public class BrowserSearch extends HomeFragment
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// Show suggestions opt-in prompt only if user hasn't been prompted
|
||||
// and we're not on a private browsing tab.
|
||||
if (!suggestionsPrompted && mSuggestClient != null) {
|
||||
// Show suggestions opt-in prompt only if suggestions are not enabled yet,
|
||||
// user hasn't been prompted and we're not on a private browsing tab.
|
||||
if (!mSuggestionsEnabled && !suggestionsPrompted && mSuggestClient != null) {
|
||||
showSuggestionsOptIn();
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
|
@ -168,7 +168,7 @@ class HomeAdapter extends FragmentStatePagerAdapter {
|
||||
args.putBoolean(HomePager.CAN_LOAD_ARG, mCanLoadHint);
|
||||
|
||||
// Only DynamicPanels need the PanelConfig argument
|
||||
if (mPanelConfig.getType() == PanelType.DYNAMIC) {
|
||||
if (mPanelConfig.isDynamic()) {
|
||||
args.putParcelable(HomePager.PANEL_CONFIG_ARG, mPanelConfig);
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@ -104,11 +106,18 @@ public final class HomeConfig {
|
||||
|
||||
public enum Flags {
|
||||
DEFAULT_PANEL,
|
||||
DISABLED_PANEL
|
||||
DISABLED_PANEL,
|
||||
DELETED_PANEL
|
||||
}
|
||||
|
||||
public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException {
|
||||
mType = PanelType.fromId(json.getString(JSON_KEY_TYPE));
|
||||
final String panelType = json.optString(JSON_KEY_TYPE, null);
|
||||
if (TextUtils.isEmpty(panelType)) {
|
||||
mType = PanelType.DYNAMIC;
|
||||
} else {
|
||||
mType = PanelType.fromId(panelType);
|
||||
}
|
||||
|
||||
mTitle = json.getString(JSON_KEY_TITLE);
|
||||
mId = json.getString(JSON_KEY_ID);
|
||||
|
||||
@ -249,6 +258,10 @@ public final class HomeConfig {
|
||||
return (mViews != null ? mViews.get(index) : null);
|
||||
}
|
||||
|
||||
public boolean isDynamic() {
|
||||
return (mType == PanelType.DYNAMIC);
|
||||
}
|
||||
|
||||
public boolean isDefault() {
|
||||
return mFlags.contains(Flags.DEFAULT_PANEL);
|
||||
}
|
||||
@ -273,6 +286,18 @@ public final class HomeConfig {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDeleted() {
|
||||
return mFlags.contains(Flags.DELETED_PANEL);
|
||||
}
|
||||
|
||||
public void setIsDeleted(boolean isDeleted) {
|
||||
if (isDeleted) {
|
||||
mFlags.add(Flags.DELETED_PANEL);
|
||||
} else {
|
||||
mFlags.remove(Flags.DELETED_PANEL);
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject toJSON() throws JSONException {
|
||||
final JSONObject json = new JSONObject();
|
||||
|
||||
@ -308,6 +333,24 @@ public final class HomeConfig {
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(o instanceof PanelConfig)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final PanelConfig other = (PanelConfig) o;
|
||||
return mId.equals(other.mId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
@ -536,6 +579,12 @@ public final class HomeConfig {
|
||||
public void setOnChangeListener(OnChangeListener listener);
|
||||
}
|
||||
|
||||
// UUIDs used to create PanelConfigs for default built-in panels
|
||||
private static final String TOP_SITES_PANEL_ID = "4becc86b-41eb-429a-a042-88fe8b5a094e";
|
||||
private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907";
|
||||
private static final String READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
|
||||
private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8";
|
||||
|
||||
private final HomeConfigBackend mBackend;
|
||||
|
||||
public HomeConfig(HomeConfigBackend backend) {
|
||||
@ -546,14 +595,56 @@ public final class HomeConfig {
|
||||
return mBackend.load();
|
||||
}
|
||||
|
||||
public void save(List<PanelConfig> entries) {
|
||||
mBackend.save(entries);
|
||||
public void save(List<PanelConfig> panelConfigs) {
|
||||
for (PanelConfig panelConfig : panelConfigs) {
|
||||
if (panelConfig.isDeleted()) {
|
||||
throw new IllegalArgumentException("Should never save a deleted PanelConfig: " + panelConfig.getId());
|
||||
}
|
||||
}
|
||||
|
||||
mBackend.save(panelConfigs);
|
||||
}
|
||||
|
||||
public void setOnChangeListener(OnChangeListener listener) {
|
||||
mBackend.setOnChangeListener(listener);
|
||||
}
|
||||
|
||||
public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType) {
|
||||
return createBuiltinPanelConfig(context, panelType, EnumSet.noneOf(PanelConfig.Flags.class));
|
||||
}
|
||||
|
||||
public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType, EnumSet<PanelConfig.Flags> flags) {
|
||||
int titleId = 0;
|
||||
String id = null;
|
||||
|
||||
switch(panelType) {
|
||||
case TOP_SITES:
|
||||
titleId = R.string.home_top_sites_title;
|
||||
id = TOP_SITES_PANEL_ID;
|
||||
break;
|
||||
|
||||
case BOOKMARKS:
|
||||
titleId = R.string.bookmarks_title;
|
||||
id = BOOKMARKS_PANEL_ID;
|
||||
break;
|
||||
|
||||
case HISTORY:
|
||||
titleId = R.string.home_history_title;
|
||||
id = HISTORY_PANEL_ID;
|
||||
break;
|
||||
|
||||
case READING_LIST:
|
||||
titleId = R.string.reading_list_title;
|
||||
id = READING_LIST_PANEL_ID;
|
||||
break;
|
||||
|
||||
case DYNAMIC:
|
||||
throw new IllegalArgumentException("createBuiltinPanelConfig() is only for built-in panels");
|
||||
}
|
||||
|
||||
return new PanelConfig(panelType, context.getString(titleId), id, flags);
|
||||
}
|
||||
|
||||
public static HomeConfig getDefault(Context context) {
|
||||
return new HomeConfig(new HomeConfigPrefsBackend(context));
|
||||
}
|
||||
|
232
mobile/android/base/home/HomeConfigInvalidator.java
Normal file
232
mobile/android/base/home/HomeConfigInvalidator.java
Normal file
@ -0,0 +1,232 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
|
||||
import org.mozilla.gecko.home.HomeConfig.PanelType;
|
||||
import org.mozilla.gecko.home.PanelManager.PanelInfo;
|
||||
import org.mozilla.gecko.home.PanelManager.RequestCallback;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import static org.mozilla.gecko.home.HomeConfig.createBuiltinPanelConfig;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class HomeConfigInvalidator implements GeckoEventListener {
|
||||
public static final String LOGTAG = "HomeConfigInvalidator";
|
||||
|
||||
private static final HomeConfigInvalidator sInstance = new HomeConfigInvalidator();
|
||||
|
||||
private static final int INVALIDATION_DELAY_MSEC = 500;
|
||||
private static final int PANEL_INFO_TIMEOUT_MSEC = 1000;
|
||||
|
||||
private static final String EVENT_HOMEPANELS_INSTALL = "HomePanels:Install";
|
||||
private static final String EVENT_HOMEPANELS_REMOVE = "HomePanels:Remove";
|
||||
|
||||
private static final String JSON_KEY_PANEL = "panel";
|
||||
|
||||
private Context mContext;
|
||||
private HomeConfig mHomeConfig;
|
||||
|
||||
private final ConcurrentLinkedQueue<PanelConfig> mPendingChanges = new ConcurrentLinkedQueue<PanelConfig>();
|
||||
private final Runnable mInvalidationRunnable = new InvalidationRunnable();
|
||||
|
||||
public static HomeConfigInvalidator getInstance() {
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public void init(Context context) {
|
||||
mContext = context;
|
||||
mHomeConfig = HomeConfig.getDefault(context);
|
||||
|
||||
GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_INSTALL, this);
|
||||
GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_REMOVE, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(String event, JSONObject message) {
|
||||
try {
|
||||
final JSONObject json = message.getJSONObject(JSON_KEY_PANEL);
|
||||
final PanelConfig panelConfig = new PanelConfig(json);
|
||||
|
||||
if (event.equals(EVENT_HOMEPANELS_INSTALL)) {
|
||||
Log.d(LOGTAG, EVENT_HOMEPANELS_INSTALL);
|
||||
handlePanelInstall(panelConfig);
|
||||
} else if (event.equals(EVENT_HOMEPANELS_REMOVE)) {
|
||||
Log.d(LOGTAG, EVENT_HOMEPANELS_REMOVE);
|
||||
handlePanelRemove(panelConfig);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Failed to handle event " + event, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the gecko thread.
|
||||
*/
|
||||
private void handlePanelInstall(PanelConfig panelConfig) {
|
||||
mPendingChanges.offer(panelConfig);
|
||||
Log.d(LOGTAG, "handlePanelInstall: " + mPendingChanges.size());
|
||||
|
||||
scheduleInvalidation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the gecko thread.
|
||||
*/
|
||||
private void handlePanelRemove(PanelConfig panelConfig) {
|
||||
panelConfig.setIsDeleted(true);
|
||||
mPendingChanges.offer(panelConfig);
|
||||
Log.d(LOGTAG, "handlePanelRemove: " + mPendingChanges.size());
|
||||
|
||||
scheduleInvalidation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the gecko or main thread.
|
||||
*/
|
||||
private void scheduleInvalidation() {
|
||||
final Handler handler = ThreadUtils.getBackgroundHandler();
|
||||
|
||||
handler.removeCallbacks(mInvalidationRunnable);
|
||||
handler.postDelayed(mInvalidationRunnable, INVALIDATION_DELAY_MSEC);
|
||||
|
||||
Log.d(LOGTAG, "scheduleInvalidation: scheduled new invalidation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the background thread.
|
||||
*/
|
||||
private List<PanelConfig> executePendingChanges(List<PanelConfig> panelConfigs) {
|
||||
while (!mPendingChanges.isEmpty()) {
|
||||
final PanelConfig panelConfig = mPendingChanges.poll();
|
||||
final String id = panelConfig.getId();
|
||||
|
||||
if (panelConfig.isDeleted()) {
|
||||
if (panelConfigs.remove(panelConfig)) {
|
||||
Log.d(LOGTAG, "executePendingChanges: removed panel " + id);
|
||||
}
|
||||
} else {
|
||||
final int index = panelConfigs.indexOf(panelConfig);
|
||||
if (index >= 0) {
|
||||
panelConfigs.set(index, panelConfig);
|
||||
Log.d(LOGTAG, "executePendingChanges: replaced position " + index + " with " + id);
|
||||
} else {
|
||||
panelConfigs.add(panelConfig);
|
||||
Log.d(LOGTAG, "executePendingChanges: added panel " + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return executeRefresh(panelConfigs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the background thread.
|
||||
*/
|
||||
private List<PanelConfig> refreshFromPanelInfos(List<PanelConfig> panelConfigs, List<PanelInfo> panelInfos) {
|
||||
Log.d(LOGTAG, "refreshFromPanelInfos");
|
||||
|
||||
final int count = panelConfigs.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final PanelConfig panelConfig = panelConfigs.get(i);
|
||||
|
||||
PanelConfig refreshedPanelConfig = null;
|
||||
if (panelConfig.isDynamic()) {
|
||||
for (PanelInfo panelInfo : panelInfos) {
|
||||
if (panelInfo.getId().equals(panelConfig.getId())) {
|
||||
refreshedPanelConfig = panelInfo.toPanelConfig();
|
||||
Log.d(LOGTAG, "refreshFromPanelInfos: refreshing from panel info: " + panelInfo.getId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refreshedPanelConfig = createBuiltinPanelConfig(mContext, panelConfig.getType());
|
||||
Log.d(LOGTAG, "refreshFromPanelInfos: refreshing built-in panel: " + panelConfig.getId());
|
||||
}
|
||||
|
||||
if (refreshedPanelConfig == null) {
|
||||
Log.d(LOGTAG, "refreshFromPanelInfos: no refreshed panel, falling back: " + panelConfig.getId());
|
||||
refreshedPanelConfig = panelConfig;
|
||||
}
|
||||
|
||||
refreshedPanelConfig.setIsDefault(panelConfig.isDefault());
|
||||
refreshedPanelConfig.setIsDisabled(panelConfig.isDisabled());
|
||||
|
||||
Log.d(LOGTAG, "refreshFromPanelInfos: set " + i + " with " + refreshedPanelConfig.getId());
|
||||
panelConfigs.set(i, refreshedPanelConfig);
|
||||
}
|
||||
|
||||
return panelConfigs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the background thread.
|
||||
*/
|
||||
private List<PanelConfig> executeRefresh(List<PanelConfig> panelConfigs) {
|
||||
if (panelConfigs.isEmpty()) {
|
||||
return panelConfigs;
|
||||
}
|
||||
|
||||
Log.d(LOGTAG, "executeRefresh");
|
||||
|
||||
final Set<String> ids = new HashSet<String>();
|
||||
for (PanelConfig panelConfig : panelConfigs) {
|
||||
ids.add(panelConfig.getId());
|
||||
}
|
||||
|
||||
final Object panelRequestLock = new Object();
|
||||
final List<PanelInfo> latestPanelInfos = new ArrayList<PanelInfo>();
|
||||
|
||||
final PanelManager pm = new PanelManager();
|
||||
pm.requestPanelsById(ids, new RequestCallback() {
|
||||
@Override
|
||||
public void onComplete(List<PanelInfo> panelInfos) {
|
||||
synchronized(panelRequestLock) {
|
||||
latestPanelInfos.addAll(panelInfos);
|
||||
Log.d(LOGTAG, "executeRefresh: fetched panel infos: " + panelInfos.size());
|
||||
|
||||
panelRequestLock.notifyAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
synchronized(panelRequestLock) {
|
||||
panelRequestLock.wait(PANEL_INFO_TIMEOUT_MSEC);
|
||||
|
||||
Log.d(LOGTAG, "executeRefresh: done fetching panel infos");
|
||||
return refreshFromPanelInfos(panelConfigs, latestPanelInfos);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(LOGTAG, "Failed to fetch panels from gecko", e);
|
||||
return panelConfigs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the background thread.
|
||||
*/
|
||||
private class InvalidationRunnable implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
mHomeConfig.save(executePendingChanges(mHomeConfig.load()));
|
||||
}
|
||||
};
|
||||
}
|
@ -13,6 +13,8 @@ import org.mozilla.gecko.home.HomeConfig.PanelType;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import static org.mozilla.gecko.home.HomeConfig.createBuiltinPanelConfig;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@ -25,7 +27,6 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
@ -34,12 +35,6 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||
|
||||
private static final String PREFS_KEY = "home_panels";
|
||||
|
||||
// UUIDs used to create PanelConfigs for default built-in panels
|
||||
private static final String TOP_SITES_PANEL_ID = "4becc86b-41eb-429a-a042-88fe8b5a094e";
|
||||
private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907";
|
||||
private static final String READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
|
||||
private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8";
|
||||
|
||||
private final Context mContext;
|
||||
private PrefsListener mPrefsListener;
|
||||
private OnChangeListener mChangeListener;
|
||||
@ -55,26 +50,18 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||
private List<PanelConfig> loadDefaultConfig() {
|
||||
final ArrayList<PanelConfig> panelConfigs = new ArrayList<PanelConfig>();
|
||||
|
||||
panelConfigs.add(new PanelConfig(PanelType.TOP_SITES,
|
||||
mContext.getString(R.string.home_top_sites_title),
|
||||
TOP_SITES_PANEL_ID,
|
||||
EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL)));
|
||||
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.TOP_SITES,
|
||||
EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL)));
|
||||
|
||||
panelConfigs.add(new PanelConfig(PanelType.BOOKMARKS,
|
||||
mContext.getString(R.string.bookmarks_title),
|
||||
BOOKMARKS_PANEL_ID));
|
||||
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.BOOKMARKS));
|
||||
|
||||
// We disable reader mode support on low memory devices. Hence the
|
||||
// reading list panel should not show up on such devices.
|
||||
if (!HardwareUtils.isLowMemoryPlatform()) {
|
||||
panelConfigs.add(new PanelConfig(PanelType.READING_LIST,
|
||||
mContext.getString(R.string.reading_list_title),
|
||||
READING_LIST_PANEL_ID));
|
||||
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.READING_LIST));
|
||||
}
|
||||
|
||||
final PanelConfig historyEntry = new PanelConfig(PanelType.HISTORY,
|
||||
mContext.getString(R.string.home_history_title),
|
||||
HISTORY_PANEL_ID);
|
||||
final PanelConfig historyEntry = createBuiltinPanelConfig(mContext, PanelType.HISTORY);
|
||||
|
||||
// On tablets, the history panel is the last.
|
||||
// On phones, the history panel is the first one.
|
||||
@ -126,7 +113,7 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||
panelConfigs = loadConfigFromString(jsonString);
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(panelConfigs);
|
||||
return panelConfigs;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -6,8 +6,11 @@
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.db.BrowserContract.HomeItems;
|
||||
import org.mozilla.gecko.home.HomeConfig.ViewConfig;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
|
||||
import org.mozilla.gecko.home.PanelLayout.PanelView;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
@ -15,18 +18,30 @@ import android.support.v4.widget.CursorAdapter;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.GridView;
|
||||
|
||||
public class PanelGridView extends GridView implements DatasetBacked {
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class PanelGridView extends GridView
|
||||
implements DatasetBacked, PanelView {
|
||||
private static final String LOGTAG = "GeckoPanelGridView";
|
||||
|
||||
private final PanelGridViewAdapter mAdapter;
|
||||
protected OnUrlOpenListener mUrlOpenListener;
|
||||
|
||||
public PanelGridView(Context context, ViewConfig viewConfig) {
|
||||
super(context, null, R.attr.panelGridViewStyle);
|
||||
mAdapter = new PanelGridViewAdapter(context);
|
||||
setAdapter(mAdapter);
|
||||
setNumColumns(AUTO_FIT);
|
||||
setOnItemClickListener(new PanelGridItemClickListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
mUrlOpenListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -34,6 +49,11 @@ public class PanelGridView extends GridView implements DatasetBacked {
|
||||
mAdapter.swapCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnUrlOpenListener(OnUrlOpenListener listener) {
|
||||
mUrlOpenListener = listener;
|
||||
}
|
||||
|
||||
private class PanelGridViewAdapter extends CursorAdapter {
|
||||
|
||||
public PanelGridViewAdapter(Context context) {
|
||||
@ -51,4 +71,19 @@ public class PanelGridView extends GridView implements DatasetBacked {
|
||||
return new PanelGridItemView(context);
|
||||
}
|
||||
}
|
||||
|
||||
private class PanelGridItemClickListener implements AdapterView.OnItemClickListener {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Cursor cursor = mAdapter.getCursor();
|
||||
if (cursor == null || !cursor.moveToPosition(position)) {
|
||||
throw new IllegalStateException("Couldn't move cursor to position " + position);
|
||||
}
|
||||
|
||||
int urlIndex = cursor.getColumnIndexOrThrow(HomeItems.URL);
|
||||
final String url = cursor.getString(urlIndex);
|
||||
|
||||
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.OPEN_WITH_INTENT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
@ -23,22 +24,38 @@ import android.util.SparseArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class PanelManager implements GeckoEventListener {
|
||||
private static final String LOGTAG = "GeckoPanelManager";
|
||||
|
||||
public class PanelInfo {
|
||||
public final String id;
|
||||
public final String title;
|
||||
public final String layout;
|
||||
public final JSONArray views;
|
||||
private final String mId;
|
||||
private final String mTitle;
|
||||
private final JSONObject mJSONData;
|
||||
|
||||
public PanelInfo(String id, String title, String layout, JSONArray views) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.layout = layout;
|
||||
this.views = views;
|
||||
public PanelInfo(String id, String title, JSONObject jsonData) {
|
||||
mId = id;
|
||||
mTitle = title;
|
||||
mJSONData = jsonData;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
public PanelConfig toPanelConfig() {
|
||||
try {
|
||||
return new PanelConfig(mJSONData);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Failed to convert PanelInfo to PanelConfig", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,11 +69,14 @@ public class PanelManager implements GeckoEventListener {
|
||||
private static final SparseArray<RequestCallback> sCallbacks = new SparseArray<RequestCallback>();
|
||||
|
||||
/**
|
||||
* Asynchronously fetches list of available panels from Gecko.
|
||||
* Asynchronously fetches list of available panels from Gecko
|
||||
* for the given IDs.
|
||||
*
|
||||
* @param ids list of panel ids to be fetched. A null value will fetch all
|
||||
* available panels.
|
||||
* @param callback onComplete will be called on the UI thread.
|
||||
*/
|
||||
public void requestAvailablePanels(RequestCallback callback) {
|
||||
public void requestPanelsById(Set<String> ids, RequestCallback callback) {
|
||||
final int requestId = sRequestId.getAndIncrement();
|
||||
|
||||
synchronized(sCallbacks) {
|
||||
@ -67,7 +87,33 @@ public class PanelManager implements GeckoEventListener {
|
||||
sCallbacks.put(requestId, callback);
|
||||
}
|
||||
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomePanels:Get", Integer.toString(requestId)));
|
||||
final JSONObject message = new JSONObject();
|
||||
try {
|
||||
message.put("requestId", requestId);
|
||||
|
||||
if (ids != null && ids.size() > 0) {
|
||||
JSONArray idsArray = new JSONArray();
|
||||
for (String id : ids) {
|
||||
idsArray.put(id);
|
||||
}
|
||||
|
||||
message.put("ids", idsArray);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Failed to build event to request panels by id", e);
|
||||
return;
|
||||
}
|
||||
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomePanels:Get", message.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously fetches list of available panels from Gecko.
|
||||
*
|
||||
* @param callback onComplete will be called on the UI thread.
|
||||
*/
|
||||
public void requestAvailablePanels(RequestCallback callback) {
|
||||
requestPanelsById(null, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,9 +158,7 @@ public class PanelManager implements GeckoEventListener {
|
||||
private PanelInfo getPanelInfoFromJSON(JSONObject jsonPanelInfo) throws JSONException {
|
||||
final String id = jsonPanelInfo.getString("id");
|
||||
final String title = jsonPanelInfo.getString("title");
|
||||
final String layout = jsonPanelInfo.getString("layout");
|
||||
final JSONArray views = jsonPanelInfo.getJSONArray("views");
|
||||
|
||||
return new PanelInfo(id, title, layout, views);
|
||||
return new PanelInfo(id, title, jsonPanelInfo);
|
||||
}
|
||||
}
|
||||
|
@ -207,6 +207,9 @@ gbjar.sources += [
|
||||
'GlobalHistory.java',
|
||||
'health/BrowserHealthRecorder.java',
|
||||
'health/BrowserHealthReporter.java',
|
||||
'health/HealthRecorder.java',
|
||||
'health/SessionInformation.java',
|
||||
'health/StubbedHealthRecorder.java',
|
||||
'home/BookmarkFolderView.java',
|
||||
'home/BookmarksListAdapter.java',
|
||||
'home/BookmarksListView.java',
|
||||
@ -219,6 +222,7 @@ gbjar.sources += [
|
||||
'home/HomeAdapter.java',
|
||||
'home/HomeBanner.java',
|
||||
'home/HomeConfig.java',
|
||||
'home/HomeConfigInvalidator.java',
|
||||
'home/HomeConfigLoader.java',
|
||||
'home/HomeConfigPrefsBackend.java',
|
||||
'home/HomeContextMenuInfo.java',
|
||||
|
@ -24,7 +24,7 @@ public class PanelsPreferenceCategory extends CustomListCategory {
|
||||
public static final String LOGTAG = "PanelsPrefCategory";
|
||||
|
||||
protected HomeConfig mHomeConfig;
|
||||
protected final List<PanelConfig> mPanelConfigs = new ArrayList<PanelConfig>();
|
||||
protected List<PanelConfig> mPanelConfigs;
|
||||
|
||||
protected UiAsyncTask<Void, Void, List<PanelConfig>> mLoadTask;
|
||||
protected UiAsyncTask<Void, Void, Void> mSaveTask;
|
||||
@ -67,17 +67,15 @@ public class PanelsPreferenceCategory extends CustomListCategory {
|
||||
|
||||
@Override
|
||||
public void onPostExecute(List<PanelConfig> panelConfigs) {
|
||||
displayPanelConfig(panelConfigs);
|
||||
mPanelConfigs = panelConfigs;
|
||||
displayPanelConfig();
|
||||
}
|
||||
};
|
||||
mLoadTask.execute();
|
||||
}
|
||||
|
||||
private void displayPanelConfig(List<PanelConfig> panelConfigs) {
|
||||
for (PanelConfig panelConfig: panelConfigs) {
|
||||
// Populate our local copy of the panels.
|
||||
mPanelConfigs.add(panelConfig);
|
||||
|
||||
private void displayPanelConfig() {
|
||||
for (PanelConfig panelConfig : mPanelConfigs) {
|
||||
// Create and add the pref.
|
||||
final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this);
|
||||
pref.setTitle(panelConfig.getTitle());
|
||||
@ -102,6 +100,10 @@ public class PanelsPreferenceCategory extends CustomListCategory {
|
||||
* @param panelConfigs Configuration to be saved
|
||||
*/
|
||||
private void saveHomeConfig() {
|
||||
if (mPanelConfigs == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<PanelConfig> panelConfigs = makeConfigListDeepCopy();
|
||||
mSaveTask = new UiAsyncTask<Void, Void, Void>(ThreadUtils.getBackgroundHandler()) {
|
||||
@Override
|
||||
|
@ -25,6 +25,7 @@ const XRE_APP_DISTRIBUTION_DIR = "XREAppDist";
|
||||
const XRE_UPDATE_ROOT_DIR = "UpdRootD";
|
||||
const ENVVAR_UPDATE_DIR = "UPDATES_DIRECTORY";
|
||||
const WEBAPPS_DIR = "webappsDir";
|
||||
const DOWNLOAD_DIR = "DfltDwnld"
|
||||
|
||||
const SYSTEM_DIST_PATH = "/system/@ANDROID_PACKAGE_NAME@/distribution";
|
||||
|
||||
@ -73,6 +74,9 @@ DirectoryProvider.prototype = {
|
||||
}
|
||||
let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
|
||||
return dm.defaultDownloadsDirectory;
|
||||
} else if (prop == DOWNLOAD_DIR) {
|
||||
let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
|
||||
return dm.defaultDownloadsDirectory;
|
||||
}
|
||||
|
||||
// We are retuning null to show failure instead for throwing an error. The
|
||||
|
@ -23,6 +23,7 @@ FilePicker.prototype = {
|
||||
_filePath: null,
|
||||
_promptActive: false,
|
||||
_filterIndex: 0,
|
||||
_addToRecentDocs: false,
|
||||
|
||||
init: function(aParent, aTitle, aMode) {
|
||||
this._domWin = aParent;
|
||||
@ -150,11 +151,11 @@ FilePicker.prototype = {
|
||||
},
|
||||
|
||||
get addToRecentDocs() {
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
return this._addToRecentDocs;
|
||||
},
|
||||
|
||||
set addToRecentDocs(val) {
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
this._addToRecentDocs = val;
|
||||
},
|
||||
|
||||
get mode() {
|
||||
|
@ -166,16 +166,27 @@ let HomePanels = {
|
||||
// Holds the currrent set of registered panels.
|
||||
_panels: {},
|
||||
|
||||
_handleGet: function(requestId) {
|
||||
_panelToJSON : function(panel) {
|
||||
return {
|
||||
id: panel.id,
|
||||
title: panel.title,
|
||||
layout: panel.layout,
|
||||
views: panel.views
|
||||
};
|
||||
},
|
||||
|
||||
_handleGet: function(data) {
|
||||
let requestId = data.requestId;
|
||||
let ids = data.ids || null;
|
||||
|
||||
let panels = [];
|
||||
for (let id in this._panels) {
|
||||
let panel = this._panels[id];
|
||||
panels.push({
|
||||
id: panel.id,
|
||||
title: panel.title,
|
||||
layout: panel.layout,
|
||||
views: panel.views
|
||||
});
|
||||
|
||||
// Null ids means we want to fetch all available panels
|
||||
if (ids == null || ids.indexOf(panel.id) >= 0) {
|
||||
panels.push(this._panelToJSON(panel));
|
||||
}
|
||||
}
|
||||
|
||||
sendMessageToJava({
|
||||
@ -211,14 +222,26 @@ let HomePanels = {
|
||||
}
|
||||
|
||||
this._panels[panel.id] = panel;
|
||||
|
||||
if (options.autoInstall) {
|
||||
sendMessageToJava({
|
||||
type: "HomePanels:Install",
|
||||
panel: this._panelToJSON(panel)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
remove: function(id) {
|
||||
if (!(id in this._panels)) {
|
||||
throw "Home.panels: Panel doesn't exist: id = " + id;
|
||||
}
|
||||
|
||||
let panel = this._panels[id];
|
||||
delete this._panels[id];
|
||||
|
||||
sendMessageToJava({
|
||||
type: "HomePanels:Remove",
|
||||
id: id
|
||||
panel: this._panelToJSON(panel)
|
||||
});
|
||||
},
|
||||
|
||||
@ -242,7 +265,7 @@ this.Home = {
|
||||
observe: function(subject, topic, data) {
|
||||
switch(topic) {
|
||||
case "HomePanels:Get":
|
||||
HomePanels._handleGet(data);
|
||||
HomePanels._handleGet(JSON.parse(data));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
const SYNC_PREFS_BRANCH = "services.sync.";
|
||||
@ -25,13 +26,25 @@ const SYNC_PREFS_BRANCH = "services.sync.";
|
||||
*
|
||||
* If Sync is not configured, no extra Sync code is loaded. If an
|
||||
* external component (say the UI) needs to interact with Sync, it
|
||||
* should do something like the following:
|
||||
* should use the promise-base function whenLoaded() - something like the
|
||||
* following:
|
||||
*
|
||||
* // 1. Grab a handle to the Sync XPCOM service.
|
||||
* let service = Cc["@mozilla.org/weave/service;1"]
|
||||
* .getService(Components.interfaces.nsISupports)
|
||||
* .wrappedJSObject;
|
||||
*
|
||||
* // 2. Use the .then method of the promise.
|
||||
* service.whenLoaded().then(() => {
|
||||
* // You are free to interact with "Weave." objects.
|
||||
* return;
|
||||
* });
|
||||
*
|
||||
* And that's it! However, if you really want to avoid promises and do it
|
||||
* old-school, then
|
||||
*
|
||||
* // 1. Get a reference to the service as done in (1) above.
|
||||
*
|
||||
* // 2. Check if the service has been initialized.
|
||||
* if (service.ready) {
|
||||
* // You are free to interact with "Weave." objects.
|
||||
@ -65,6 +78,20 @@ WeaveService.prototype = {
|
||||
Weave.Service;
|
||||
},
|
||||
|
||||
whenLoaded: function() {
|
||||
if (this.ready) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
let deferred = Promise.defer();
|
||||
|
||||
Services.obs.addObserver(function onReady() {
|
||||
Services.obs.removeObserver(onReady, "weave:service:ready");
|
||||
deferred.resolve();
|
||||
}, "weave:service:ready", false);
|
||||
this.ensureLoaded();
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
get fxAccountsEnabled() {
|
||||
// work out what identity manager to use. This is stored in a preference;
|
||||
// if the preference exists, we trust it.
|
||||
|
@ -164,6 +164,7 @@ this.BrowserIDManager.prototype = {
|
||||
},
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
this._log.debug("observed " + topic);
|
||||
switch (topic) {
|
||||
case fxAccountsCommon.ONLOGIN_NOTIFICATION:
|
||||
this.initializeWithCurrentIdentity(true);
|
||||
@ -409,15 +410,17 @@ this.BrowserIDManager.prototype = {
|
||||
// Both Jelly and FxAccounts give us kB as hex
|
||||
let kBbytes = CommonUtils.hexToBytes(userData.kB);
|
||||
let headers = {"X-Client-State": this._computeXClientState(kBbytes)};
|
||||
log.info("Fetching Sync token from: " + tokenServerURI);
|
||||
log.info("Fetching assertion and token from: " + tokenServerURI);
|
||||
|
||||
function getToken(tokenServerURI, assertion) {
|
||||
log.debug("Getting a token");
|
||||
let deferred = Promise.defer();
|
||||
let cb = function (err, token) {
|
||||
if (err) {
|
||||
log.info("TokenServerClient.getTokenFromBrowserIDAssertion() failed with: " + err.message);
|
||||
return deferred.reject(new AuthenticationError(err.message));
|
||||
} else {
|
||||
log.debug("Successfully got a sync token");
|
||||
return deferred.resolve(token);
|
||||
}
|
||||
};
|
||||
@ -427,6 +430,7 @@ this.BrowserIDManager.prototype = {
|
||||
}
|
||||
|
||||
function getAssertion() {
|
||||
log.debug("Getting an assertion");
|
||||
let audience = Services.io.newURI(tokenServerURI, null, null).prePath;
|
||||
return fxAccounts.getAssertion(audience).then(null, err => {
|
||||
if (err.code === 401) {
|
||||
|
@ -1844,26 +1844,39 @@ function appendSectionHeader(aP, aText)
|
||||
function saveReportsToFile()
|
||||
{
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window, "Save Memory Reports", Ci.nsIFilePicker.modeSave);
|
||||
fp.appendFilter("Zipped JSON files", "*.json.gz");
|
||||
fp.appendFilters(Ci.nsIFilePicker.filterAll);
|
||||
fp.filterIndex = 0;
|
||||
fp.addToRecentDocs = true;
|
||||
fp.defaultString = "memory-report.json.gz";
|
||||
|
||||
let fpFinish = function(file) {
|
||||
let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
|
||||
.getService(Ci.nsIMemoryInfoDumper);
|
||||
|
||||
let finishDumping = () => {
|
||||
updateMainAndFooter("Saved reports to " + file.path, HIDE_FOOTER);
|
||||
}
|
||||
|
||||
dumper.dumpMemoryReportsToNamedFile(file.path, finishDumping, null);
|
||||
}
|
||||
|
||||
let fpCallback = function(aResult) {
|
||||
if (aResult == Ci.nsIFilePicker.returnOK ||
|
||||
aResult == Ci.nsIFilePicker.returnReplace) {
|
||||
|
||||
let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
|
||||
.getService(Ci.nsIMemoryInfoDumper);
|
||||
|
||||
let finishDumping = () => {
|
||||
updateMainAndFooter("Saved reports to " + fp.file.path, HIDE_FOOTER);
|
||||
}
|
||||
|
||||
dumper.dumpMemoryReportsToNamedFile(fp.file.path, finishDumping, null);
|
||||
fpFinish(fp.file);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
fp.init(window, "Save Memory Reports", Ci.nsIFilePicker.modeSave);
|
||||
} catch(ex) {
|
||||
// This will fail on Android, since there is no Save as file picker there.
|
||||
// Just save to the default downloads dir if it does.
|
||||
let file = Services.dirsvc.get("DfltDwnld", Ci.nsIFile);
|
||||
file.append(fp.defaultString);
|
||||
fpFinish(file);
|
||||
return;
|
||||
}
|
||||
fp.open(fpCallback);
|
||||
}
|
||||
|
@ -22,11 +22,11 @@
|
||||
</stack>
|
||||
|
||||
<panel id="panel" type="arrow" onpopupshown="checkPanelPosition(this)" onpopuphidden="runNextTest.next()">
|
||||
<label id="panellabel" value="This is some text." height="65"/>
|
||||
<label id="panellabel" value="This is some text..." height="65"/>
|
||||
</panel>
|
||||
|
||||
<panel id="bigpanel" type="arrow" onpopupshown="checkBigPanel(this)" onpopuphidden="runNextTest.next()">
|
||||
<button label="This is some text." height="3000"/>
|
||||
<button label="This is some text..." height="3000"/>
|
||||
</panel>
|
||||
|
||||
<script type="application/javascript">
|
||||
|
@ -20,14 +20,14 @@
|
||||
// [dammit]: acorn_loose.js
|
||||
// [walk]: util/walk.js
|
||||
|
||||
(function(mod) {
|
||||
(function(root, mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS
|
||||
if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD
|
||||
mod(self.acorn || (self.acorn = {})); // Plain browser env
|
||||
})(function(exports) {
|
||||
mod(root.acorn || (root.acorn = {})); // Plain browser env
|
||||
})(this, function(exports) {
|
||||
"use strict";
|
||||
|
||||
exports.version = "0.1.01";
|
||||
exports.version = "0.4.1";
|
||||
|
||||
// The main exported interface (under `self.acorn` when in the
|
||||
// browser) is a `parse` function that takes a code string and
|
||||
@ -77,7 +77,8 @@
|
||||
// character offsets that denote the start and end of the comment.
|
||||
// When the `locations` option is on, two more parameters are
|
||||
// passed, the full `{line, column}` locations of the start and
|
||||
// end of the comments.
|
||||
// end of the comments. Note that you are not allowed to call the
|
||||
// parser from the callback—that will corrupt its internal state.
|
||||
onComment: null,
|
||||
// Nodes have their start and end characters offsets recorded in
|
||||
// `start` and `end` properties (directly on the node, rather than
|
||||
@ -94,14 +95,17 @@
|
||||
// toplevel forms of the parsed file to the `Program` (top) node
|
||||
// of an existing parse tree.
|
||||
program: null,
|
||||
// When `location` is on, you can pass this to record the source
|
||||
// When `locations` is on, you can pass this to record the source
|
||||
// file in every node's `loc` object.
|
||||
sourceFile: null
|
||||
sourceFile: null,
|
||||
// This value, if given, is stored in every node, whether
|
||||
// `locations` is on or off.
|
||||
directSourceFile: null
|
||||
};
|
||||
|
||||
function setOptions(opts) {
|
||||
options = opts || {};
|
||||
for (var opt in defaultOptions) if (!options.hasOwnProperty(opt))
|
||||
for (var opt in defaultOptions) if (!Object.prototype.hasOwnProperty.call(options, opt))
|
||||
options[opt] = defaultOptions[opt];
|
||||
sourceFile = options.sourceFile || null;
|
||||
}
|
||||
@ -138,6 +142,7 @@
|
||||
|
||||
var t = {};
|
||||
function getToken(forceRegexp) {
|
||||
lastEnd = tokEnd;
|
||||
readToken(forceRegexp);
|
||||
t.start = tokStart; t.end = tokEnd;
|
||||
t.startLoc = tokStartLoc; t.endLoc = tokEndLoc;
|
||||
@ -147,14 +152,14 @@
|
||||
getToken.jumpTo = function(pos, reAllowed) {
|
||||
tokPos = pos;
|
||||
if (options.locations) {
|
||||
tokCurLine = tokLineStart = lineBreak.lastIndex = 0;
|
||||
tokCurLine = 1;
|
||||
tokLineStart = lineBreak.lastIndex = 0;
|
||||
var match;
|
||||
while ((match = lineBreak.exec(input)) && match.index < pos) {
|
||||
++tokCurLine;
|
||||
tokLineStart = match.index + match[0].length;
|
||||
}
|
||||
}
|
||||
var ch = input.charAt(pos - 1);
|
||||
tokRegexpAllowed = reAllowed;
|
||||
skipSpace();
|
||||
};
|
||||
@ -228,6 +233,10 @@
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Reused empty array added for node fields that are always empty.
|
||||
|
||||
var empty = [];
|
||||
|
||||
// ## Token types
|
||||
|
||||
// The assignment of fine-grained, information-carrying type objects
|
||||
@ -313,13 +322,18 @@
|
||||
// in AssignmentExpression nodes.
|
||||
|
||||
var _slash = {binop: 10, beforeExpr: true}, _eq = {isAssign: true, beforeExpr: true};
|
||||
var _assign = {isAssign: true, beforeExpr: true}, _plusmin = {binop: 9, prefix: true, beforeExpr: true};
|
||||
var _incdec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true};
|
||||
var _bin1 = {binop: 1, beforeExpr: true}, _bin2 = {binop: 2, beforeExpr: true};
|
||||
var _bin3 = {binop: 3, beforeExpr: true}, _bin4 = {binop: 4, beforeExpr: true};
|
||||
var _bin5 = {binop: 5, beforeExpr: true}, _bin6 = {binop: 6, beforeExpr: true};
|
||||
var _bin7 = {binop: 7, beforeExpr: true}, _bin8 = {binop: 8, beforeExpr: true};
|
||||
var _bin10 = {binop: 10, beforeExpr: true};
|
||||
var _assign = {isAssign: true, beforeExpr: true};
|
||||
var _incDec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true};
|
||||
var _logicalOR = {binop: 1, beforeExpr: true};
|
||||
var _logicalAND = {binop: 2, beforeExpr: true};
|
||||
var _bitwiseOR = {binop: 3, beforeExpr: true};
|
||||
var _bitwiseXOR = {binop: 4, beforeExpr: true};
|
||||
var _bitwiseAND = {binop: 5, beforeExpr: true};
|
||||
var _equality = {binop: 6, beforeExpr: true};
|
||||
var _relational = {binop: 7, beforeExpr: true};
|
||||
var _bitShift = {binop: 8, beforeExpr: true};
|
||||
var _plusMin = {binop: 9, prefix: true, beforeExpr: true};
|
||||
var _multiplyModulo = {binop: 10, beforeExpr: true};
|
||||
|
||||
// Provide access to the token types for external users of the
|
||||
// tokenizer.
|
||||
@ -328,7 +342,7 @@
|
||||
parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon,
|
||||
dot: _dot, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof,
|
||||
num: _num, regexp: _regexp, string: _string};
|
||||
for (var kw in keywordTypes) exports.tokTypes[kw] = keywordTypes[kw];
|
||||
for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw];
|
||||
|
||||
// This is a trick taken from Esprima. It turns out that, on
|
||||
// non-Chrome browsers, to check whether a string is in a set, a
|
||||
@ -405,9 +419,9 @@
|
||||
// are only applied when a character is found to actually have a
|
||||
// code point above 128.
|
||||
|
||||
var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/;
|
||||
var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/;
|
||||
var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc";
|
||||
var nonASCIIidentifierChars = "\u0371-\u0374\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f";
|
||||
var nonASCIIidentifierChars = "\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f";
|
||||
var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
|
||||
var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
|
||||
|
||||
@ -422,17 +436,17 @@
|
||||
|
||||
// Test whether a given character code starts an identifier.
|
||||
|
||||
function isIdentifierStart(code) {
|
||||
var isIdentifierStart = exports.isIdentifierStart = function(code) {
|
||||
if (code < 65) return code === 36;
|
||||
if (code < 91) return true;
|
||||
if (code < 97) return code === 95;
|
||||
if (code < 123)return true;
|
||||
return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
|
||||
}
|
||||
};
|
||||
|
||||
// Test whether a given character is part of an identifier.
|
||||
|
||||
function isIdentifierChar(code) {
|
||||
var isIdentifierChar = exports.isIdentifierChar = function(code) {
|
||||
if (code < 48) return code === 36;
|
||||
if (code < 58) return true;
|
||||
if (code < 65) return false;
|
||||
@ -440,7 +454,7 @@
|
||||
if (code < 97) return code === 95;
|
||||
if (code < 123)return true;
|
||||
return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
|
||||
}
|
||||
};
|
||||
|
||||
// ## Tokenizer
|
||||
|
||||
@ -496,7 +510,7 @@
|
||||
var start = tokPos;
|
||||
var startLoc = options.onComment && options.locations && new line_loc_t;
|
||||
var ch = input.charCodeAt(tokPos+=2);
|
||||
while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8329) {
|
||||
while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) {
|
||||
++tokPos;
|
||||
ch = input.charCodeAt(tokPos);
|
||||
}
|
||||
@ -513,30 +527,32 @@
|
||||
var ch = input.charCodeAt(tokPos);
|
||||
if (ch === 32) { // ' '
|
||||
++tokPos;
|
||||
} else if(ch === 13) {
|
||||
} else if (ch === 13) {
|
||||
++tokPos;
|
||||
var next = input.charCodeAt(tokPos);
|
||||
if(next === 10) {
|
||||
if (next === 10) {
|
||||
++tokPos;
|
||||
}
|
||||
if(options.locations) {
|
||||
if (options.locations) {
|
||||
++tokCurLine;
|
||||
tokLineStart = tokPos;
|
||||
}
|
||||
} else if (ch === 10) {
|
||||
} else if (ch === 10 || ch === 8232 || ch === 8233) {
|
||||
++tokPos;
|
||||
++tokCurLine;
|
||||
tokLineStart = tokPos;
|
||||
} else if(ch < 14 && ch > 8) {
|
||||
if (options.locations) {
|
||||
++tokCurLine;
|
||||
tokLineStart = tokPos;
|
||||
}
|
||||
} else if (ch > 8 && ch < 14) {
|
||||
++tokPos;
|
||||
} else if (ch === 47) { // '/'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next === 42) { // '*'
|
||||
skipBlockComment();
|
||||
} else if (next === 47) { // '/'
|
||||
skipLineComment();
|
||||
} else break;
|
||||
} else if ((ch < 14 && ch > 8) || ch === 32 || ch === 160) { // ' ', '\xa0'
|
||||
} else if (ch === 160) { // '\xa0'
|
||||
++tokPos;
|
||||
} else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {
|
||||
++tokPos;
|
||||
@ -559,61 +575,79 @@
|
||||
// `tokRegexpAllowed` trick does not work. See `parseStatement`.
|
||||
|
||||
function readToken_dot() {
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next >= 48 && next <= 57) return readNumber(true);
|
||||
++tokPos;
|
||||
return finishToken(_dot);
|
||||
}
|
||||
|
||||
function readToken_slash() { // '/'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (tokRegexpAllowed) {++tokPos; return readRegexp();}
|
||||
if (next === 61) return finishOp(_assign, 2);
|
||||
return finishOp(_slash, 1);
|
||||
}
|
||||
|
||||
function readToken_mult_modulo() { // '%*'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next === 61) return finishOp(_assign, 2);
|
||||
return finishOp(_bin10, 1);
|
||||
return finishOp(_multiplyModulo, 1);
|
||||
}
|
||||
|
||||
function readToken_pipe_amp(code) { // '|&'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
if (next === code) return finishOp(code === 124 ? _bin1 : _bin2, 2);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next === code) return finishOp(code === 124 ? _logicalOR : _logicalAND, 2);
|
||||
if (next === 61) return finishOp(_assign, 2);
|
||||
return finishOp(code === 124 ? _bin3 : _bin5, 1);
|
||||
return finishOp(code === 124 ? _bitwiseOR : _bitwiseAND, 1);
|
||||
}
|
||||
|
||||
function readToken_caret() { // '^'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next === 61) return finishOp(_assign, 2);
|
||||
return finishOp(_bin4, 1);
|
||||
return finishOp(_bitwiseXOR, 1);
|
||||
}
|
||||
|
||||
function readToken_plus_min(code) { // '+-'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
if (next === code) return finishOp(_incdec, 2);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next === code) {
|
||||
if (next == 45 && input.charCodeAt(tokPos + 2) == 62 &&
|
||||
newline.test(input.slice(lastEnd, tokPos))) {
|
||||
// A `-->` line comment
|
||||
tokPos += 3;
|
||||
skipLineComment();
|
||||
skipSpace();
|
||||
return readToken();
|
||||
}
|
||||
return finishOp(_incDec, 2);
|
||||
}
|
||||
if (next === 61) return finishOp(_assign, 2);
|
||||
return finishOp(_plusmin, 1);
|
||||
return finishOp(_plusMin, 1);
|
||||
}
|
||||
|
||||
function readToken_lt_gt(code) { // '<>'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
var size = 1;
|
||||
if (next === code) {
|
||||
size = code === 62 && input.charCodeAt(tokPos+2) === 62 ? 3 : 2;
|
||||
size = code === 62 && input.charCodeAt(tokPos + 2) === 62 ? 3 : 2;
|
||||
if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1);
|
||||
return finishOp(_bin8, size);
|
||||
return finishOp(_bitShift, size);
|
||||
}
|
||||
if (next == 33 && code == 60 && input.charCodeAt(tokPos + 2) == 45 &&
|
||||
input.charCodeAt(tokPos + 3) == 45) {
|
||||
// `<!--`, an XML-style comment that should be interpreted as a line comment
|
||||
tokPos += 4;
|
||||
skipLineComment();
|
||||
skipSpace();
|
||||
return readToken();
|
||||
}
|
||||
if (next === 61)
|
||||
size = input.charCodeAt(tokPos+2) === 61 ? 3 : 2;
|
||||
return finishOp(_bin7, size);
|
||||
size = input.charCodeAt(tokPos + 2) === 61 ? 3 : 2;
|
||||
return finishOp(_relational, size);
|
||||
}
|
||||
|
||||
|
||||
function readToken_eq_excl(code) { // '=!'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
if (next === 61) return finishOp(_bin6, input.charCodeAt(tokPos+2) === 61 ? 3 : 2);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next === 61) return finishOp(_equality, input.charCodeAt(tokPos + 2) === 61 ? 3 : 2);
|
||||
return finishOp(code === 61 ? _eq : _prefix, 1);
|
||||
}
|
||||
|
||||
@ -638,7 +672,7 @@
|
||||
|
||||
// '0x' is a hexadecimal number.
|
||||
case 48: // '0'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next === 120 || next === 88) return readHexNumber();
|
||||
// Anything else beginning with a digit is an integer, octal
|
||||
// number, or float.
|
||||
@ -693,7 +727,7 @@
|
||||
// Identifier or keyword. '\uXXXX' sequences are allowed in
|
||||
// identifiers, so '\' also dispatches to that.
|
||||
if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord();
|
||||
|
||||
|
||||
var tok = getTokenFromCode(code);
|
||||
|
||||
if (tok === false) {
|
||||
@ -702,7 +736,7 @@
|
||||
var ch = String.fromCharCode(code);
|
||||
if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord();
|
||||
raise(tokPos, "Unexpected character '" + ch + "'");
|
||||
}
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
@ -735,7 +769,13 @@
|
||||
// here (don't ask).
|
||||
var mods = readWord1();
|
||||
if (mods && !/^[gmsiy]*$/.test(mods)) raise(start, "Invalid regexp flag");
|
||||
return finishToken(_regexp, new RegExp(content, mods));
|
||||
try {
|
||||
var value = new RegExp(content, mods);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) raise(start, e.message);
|
||||
raise(e);
|
||||
}
|
||||
return finishToken(_regexp, value);
|
||||
}
|
||||
|
||||
// Read an integer in the given radix. Return null if zero digits
|
||||
@ -768,7 +808,7 @@
|
||||
}
|
||||
|
||||
// Read an integer, octal integer, or floating-point number.
|
||||
|
||||
|
||||
function readNumber(startsWithDot) {
|
||||
var start = tokPos, isFloat = false, octal = input.charCodeAt(tokPos) === 48;
|
||||
if (!startsWithDot && readInt(10) === null) raise(start, "Invalid number");
|
||||
@ -781,7 +821,7 @@
|
||||
if (next === 69 || next === 101) { // 'eE'
|
||||
next = input.charCodeAt(++tokPos);
|
||||
if (next === 43 || next === 45) ++tokPos; // '+-'
|
||||
if (readInt(10) === null) raise(start, "Invalid number")
|
||||
if (readInt(10) === null) raise(start, "Invalid number");
|
||||
isFloat = true;
|
||||
}
|
||||
if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
|
||||
@ -810,7 +850,7 @@
|
||||
ch = input.charCodeAt(++tokPos);
|
||||
var octal = /^[0-7]+/.exec(input.slice(tokPos, tokPos + 3));
|
||||
if (octal) octal = octal[0];
|
||||
while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, octal.length - 1);
|
||||
while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, -1);
|
||||
if (octal === "0") octal = null;
|
||||
++tokPos;
|
||||
if (octal) {
|
||||
@ -837,7 +877,7 @@
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (ch === 13 || ch === 10 || ch === 8232 || ch === 8329) raise(tokStart, "Unterminated string constant");
|
||||
if (ch === 13 || ch === 10 || ch === 8232 || ch === 8233) raise(tokStart, "Unterminated string constant");
|
||||
out += String.fromCharCode(ch); // '\'
|
||||
++tokPos;
|
||||
}
|
||||
@ -931,7 +971,7 @@
|
||||
// ### Parser utilities
|
||||
|
||||
// Continue to the next token.
|
||||
|
||||
|
||||
function next() {
|
||||
lastStart = tokStart;
|
||||
lastEnd = tokEnd;
|
||||
@ -944,10 +984,12 @@
|
||||
|
||||
function setStrict(strct) {
|
||||
strict = strct;
|
||||
tokPos = lastEnd;
|
||||
while (tokPos < tokLineStart) {
|
||||
tokLineStart = input.lastIndexOf("\n", tokLineStart - 2) + 1;
|
||||
--tokCurLine;
|
||||
tokPos = tokStart;
|
||||
if (options.locations) {
|
||||
while (tokPos < tokLineStart) {
|
||||
tokLineStart = input.lastIndexOf("\n", tokLineStart - 2) + 1;
|
||||
--tokCurLine;
|
||||
}
|
||||
}
|
||||
skipSpace();
|
||||
readToken();
|
||||
@ -971,6 +1013,8 @@
|
||||
var node = new node_t();
|
||||
if (options.locations)
|
||||
node.loc = new node_loc_t();
|
||||
if (options.directSourceFile)
|
||||
node.sourceFile = options.directSourceFile;
|
||||
if (options.ranges)
|
||||
node.range = [tokStart, 0];
|
||||
return node;
|
||||
@ -1095,7 +1139,7 @@
|
||||
// does not help.
|
||||
|
||||
function parseStatement() {
|
||||
if (tokType === _slash)
|
||||
if (tokType === _slash || tokType === _assign && tokVal == "/=")
|
||||
readToken(true);
|
||||
|
||||
var starttype = tokType, node = startNode();
|
||||
@ -1159,6 +1203,7 @@
|
||||
var init = startNode();
|
||||
next();
|
||||
parseVar(init, true);
|
||||
finishNode(init, "VariableDeclaration");
|
||||
if (init.declarations.length === 1 && eat(_in))
|
||||
return parseForIn(node, init);
|
||||
return parseFor(node, init);
|
||||
@ -1185,7 +1230,7 @@
|
||||
// In `return` (and `break`/`continue`), the keywords with
|
||||
// optional arguments, we eagerly look for a semicolon or the
|
||||
// possibility to insert one.
|
||||
|
||||
|
||||
if (eat(_semi) || canInsertSemicolon()) node.argument = null;
|
||||
else { node.argument = parseExpression(); semicolon(); }
|
||||
return finishNode(node, "ReturnStatement");
|
||||
@ -1200,7 +1245,7 @@
|
||||
// Statements under must be grouped (by label) in SwitchCase
|
||||
// nodes. `cur` is used to keep the node that we are currently
|
||||
// adding statements to.
|
||||
|
||||
|
||||
for (var cur, sawDefault; tokType != _braceR;) {
|
||||
if (tokType === _case || tokType === _default) {
|
||||
var isCase = tokType === _case;
|
||||
@ -1248,6 +1293,7 @@
|
||||
clause.body = parseBlock();
|
||||
node.handler = finishNode(clause, "CatchClause");
|
||||
}
|
||||
node.guardedHandlers = empty;
|
||||
node.finalizer = eat(_finally) ? parseBlock() : null;
|
||||
if (!node.handler && !node.finalizer)
|
||||
raise(node.start, "Missing catch or finally clause");
|
||||
@ -1255,9 +1301,9 @@
|
||||
|
||||
case _var:
|
||||
next();
|
||||
node = parseVar(node);
|
||||
parseVar(node);
|
||||
semicolon();
|
||||
return node;
|
||||
return finishNode(node, "VariableDeclaration");
|
||||
|
||||
case _while:
|
||||
next();
|
||||
@ -1327,11 +1373,11 @@
|
||||
while (!eat(_braceR)) {
|
||||
var stmt = parseStatement();
|
||||
node.body.push(stmt);
|
||||
if (first && isUseStrict(stmt)) {
|
||||
if (first && allowStrict && isUseStrict(stmt)) {
|
||||
oldStrict = strict;
|
||||
setStrict(strict = true);
|
||||
}
|
||||
first = false
|
||||
first = false;
|
||||
}
|
||||
if (strict && !oldStrict) setStrict(false);
|
||||
return finishNode(node, "BlockStatement");
|
||||
@ -1378,7 +1424,7 @@
|
||||
node.declarations.push(finishNode(decl, "VariableDeclarator"));
|
||||
if (!eat(_comma)) break;
|
||||
}
|
||||
return finishNode(node, "VariableDeclaration");
|
||||
return node;
|
||||
}
|
||||
|
||||
// ### Expression parsing
|
||||
@ -1439,7 +1485,7 @@
|
||||
// Start the precedence parser.
|
||||
|
||||
function parseExprOps(noIn) {
|
||||
return parseExprOp(parseMaybeUnary(noIn), -1, noIn);
|
||||
return parseExprOp(parseMaybeUnary(), -1, noIn);
|
||||
}
|
||||
|
||||
// Parse binary operators with the operator precedence parsing
|
||||
@ -1455,10 +1501,11 @@
|
||||
var node = startNodeFrom(left);
|
||||
node.left = left;
|
||||
node.operator = tokVal;
|
||||
var op = tokType;
|
||||
next();
|
||||
node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn);
|
||||
var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression");
|
||||
return parseExprOp(node, minPrec, noIn);
|
||||
node.right = parseExprOp(parseMaybeUnary(), prec, noIn);
|
||||
var exprNode = finishNode(node, (op === _logicalOR || op === _logicalAND) ? "LogicalExpression" : "BinaryExpression");
|
||||
return parseExprOp(exprNode, minPrec, noIn);
|
||||
}
|
||||
}
|
||||
return left;
|
||||
@ -1466,13 +1513,14 @@
|
||||
|
||||
// Parse unary operators, both prefix and postfix.
|
||||
|
||||
function parseMaybeUnary(noIn) {
|
||||
function parseMaybeUnary() {
|
||||
if (tokType.prefix) {
|
||||
var node = startNode(), update = tokType.isUpdate;
|
||||
node.operator = tokVal;
|
||||
node.prefix = true;
|
||||
tokRegexpAllowed = true;
|
||||
next();
|
||||
node.argument = parseMaybeUnary(noIn);
|
||||
node.argument = parseMaybeUnary();
|
||||
if (update) checkLVal(node.argument);
|
||||
else if (strict && node.operator === "delete" &&
|
||||
node.argument.type === "Identifier")
|
||||
@ -1543,7 +1591,7 @@
|
||||
case _null: case _true: case _false:
|
||||
var node = startNode();
|
||||
node.value = tokType.atomValue;
|
||||
node.raw = tokType.keyword
|
||||
node.raw = tokType.keyword;
|
||||
next();
|
||||
return finishNode(node, "Literal");
|
||||
|
||||
@ -1586,14 +1634,14 @@
|
||||
|
||||
// New's precedence is slightly tricky. It must allow its argument
|
||||
// to be a `[]` or dot subscript expression, but not a call — at
|
||||
// least, not without wrapping it in parentheses. Thus, it uses the
|
||||
// least, not without wrapping it in parentheses. Thus, it uses the
|
||||
|
||||
function parseNew() {
|
||||
var node = startNode();
|
||||
next();
|
||||
node.callee = parseSubscripts(parseExprAtom(), true);
|
||||
if (eat(_parenL)) node.arguments = parseExprList(_parenR, false);
|
||||
else node.arguments = [];
|
||||
else node.arguments = empty;
|
||||
return finishNode(node, "NewExpression");
|
||||
}
|
||||
|
||||
@ -1712,6 +1760,7 @@
|
||||
function parseIdent(liberal) {
|
||||
var node = startNode();
|
||||
node.name = tokType === _name ? tokVal : (liberal && !options.forbidReserved && tokType.keyword) || unexpected();
|
||||
tokRegexpAllowed = false;
|
||||
next();
|
||||
return finishNode(node, "Identifier");
|
||||
}
|
||||
|
@ -29,11 +29,11 @@
|
||||
// invasive changes and simplifications without creating a complicated
|
||||
// tangle.
|
||||
|
||||
(function(mod) {
|
||||
(function(root, mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") return mod(exports, require("./acorn")); // CommonJS
|
||||
if (typeof define == "function" && define.amd) return define(["exports", "./acorn"], mod); // AMD
|
||||
mod(self.acorn || (self.acorn = {}), self.acorn); // Plain browser env
|
||||
})(function(exports, acorn) {
|
||||
mod(root.acorn || (root.acorn = {}), root.acorn); // Plain browser env
|
||||
})(this, function(exports, acorn) {
|
||||
"use strict";
|
||||
|
||||
var tt = acorn.tokTypes;
|
||||
@ -168,10 +168,6 @@
|
||||
while (pos < input.length && !isNewline(input.charCodeAt(pos))) ++pos;
|
||||
return pos;
|
||||
}
|
||||
function lineStart(pos) {
|
||||
while (pos > 0 && !isNewline(input.charCodeAt(pos - 1))) --pos;
|
||||
return pos;
|
||||
}
|
||||
function indentationAfter(pos) {
|
||||
for (var count = 0;; ++pos) {
|
||||
var ch = input.charCodeAt(pos);
|
||||
@ -181,16 +177,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
function closesBlock(closeTok, indent, line) {
|
||||
function closes(closeTok, indent, line, blockHeuristic) {
|
||||
if (token.type === closeTok || token.type === tt.eof) return true;
|
||||
if (line != curLineStart && curIndent < indent && tokenStartsLine() &&
|
||||
(nextLineStart >= input.length ||
|
||||
(!blockHeuristic || nextLineStart >= input.length ||
|
||||
indentationAfter(nextLineStart) < indent)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function tokenStartsLine() {
|
||||
for (var p = token.start - 1; p > curLineStart; --p) {
|
||||
for (var p = token.start - 1; p >= curLineStart; --p) {
|
||||
var ch = input.charCodeAt(p);
|
||||
if (ch !== 9 && ch !== 32) return false;
|
||||
}
|
||||
@ -213,7 +209,9 @@
|
||||
var node = new node_t(token.start);
|
||||
if (options.locations)
|
||||
node.loc = new node_loc_t();
|
||||
return node
|
||||
if (options.directSourceFile)
|
||||
node.sourceFile = options.directSourceFile;
|
||||
return node;
|
||||
}
|
||||
|
||||
function startNodeFrom(other) {
|
||||
@ -291,60 +289,60 @@
|
||||
var starttype = token.type, node = startNode();
|
||||
|
||||
switch (starttype) {
|
||||
case tt.break: case tt.continue:
|
||||
case tt._break: case tt._continue:
|
||||
next();
|
||||
var isBreak = starttype === tt.break;
|
||||
var isBreak = starttype === tt._break;
|
||||
node.label = token.type === tt.name ? parseIdent() : null;
|
||||
semicolon();
|
||||
return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
|
||||
|
||||
case tt.debugger:
|
||||
case tt._debugger:
|
||||
next();
|
||||
semicolon();
|
||||
return finishNode(node, "DebuggerStatement");
|
||||
|
||||
case tt.do:
|
||||
case tt._do:
|
||||
next();
|
||||
node.body = parseStatement();
|
||||
node.test = eat(tt.while) ? parseParenExpression() : dummyIdent();
|
||||
node.test = eat(tt._while) ? parseParenExpression() : dummyIdent();
|
||||
semicolon();
|
||||
return finishNode(node, "DoWhileStatement");
|
||||
|
||||
case tt.for:
|
||||
case tt._for:
|
||||
next();
|
||||
pushCx();
|
||||
expect(tt.parenL);
|
||||
if (token.type === tt.semi) return parseFor(node, null);
|
||||
if (token.type === tt.var) {
|
||||
if (token.type === tt._var) {
|
||||
var init = startNode();
|
||||
next();
|
||||
parseVar(init, true);
|
||||
if (init.declarations.length === 1 && eat(tt.in))
|
||||
if (init.declarations.length === 1 && eat(tt._in))
|
||||
return parseForIn(node, init);
|
||||
return parseFor(node, init);
|
||||
}
|
||||
var init = parseExpression(false, true);
|
||||
if (eat(tt.in)) {return parseForIn(node, checkLVal(init));}
|
||||
if (eat(tt._in)) {return parseForIn(node, checkLVal(init));}
|
||||
return parseFor(node, init);
|
||||
|
||||
case tt.function:
|
||||
case tt._function:
|
||||
next();
|
||||
return parseFunction(node, true);
|
||||
|
||||
case tt.if:
|
||||
case tt._if:
|
||||
next();
|
||||
node.test = parseParenExpression();
|
||||
node.consequent = parseStatement();
|
||||
node.alternate = eat(tt.else) ? parseStatement() : null;
|
||||
node.alternate = eat(tt._else) ? parseStatement() : null;
|
||||
return finishNode(node, "IfStatement");
|
||||
|
||||
case tt.return:
|
||||
case tt._return:
|
||||
next();
|
||||
if (eat(tt.semi) || canInsertSemicolon()) node.argument = null;
|
||||
else { node.argument = parseExpression(); semicolon(); }
|
||||
return finishNode(node, "ReturnStatement");
|
||||
|
||||
case tt.switch:
|
||||
case tt._switch:
|
||||
var blockIndent = curIndent, line = curLineStart;
|
||||
next();
|
||||
node.discriminant = parseParenExpression();
|
||||
@ -352,9 +350,9 @@
|
||||
pushCx();
|
||||
expect(tt.braceL);
|
||||
|
||||
for (var cur; !closesBlock(tt.braceR, blockIndent, line);) {
|
||||
if (token.type === tt.case || token.type === tt.default) {
|
||||
var isCase = token.type === tt.case;
|
||||
for (var cur; !closes(tt.braceR, blockIndent, line, true);) {
|
||||
if (token.type === tt._case || token.type === tt._default) {
|
||||
var isCase = token.type === tt._case;
|
||||
if (cur) finishNode(cur, "SwitchCase");
|
||||
node.cases.push(cur = startNode());
|
||||
cur.consequent = [];
|
||||
@ -376,17 +374,17 @@
|
||||
eat(tt.braceR);
|
||||
return finishNode(node, "SwitchStatement");
|
||||
|
||||
case tt.throw:
|
||||
case tt._throw:
|
||||
next();
|
||||
node.argument = parseExpression();
|
||||
semicolon();
|
||||
return finishNode(node, "ThrowStatement");
|
||||
|
||||
case tt.try:
|
||||
case tt._try:
|
||||
next();
|
||||
node.block = parseBlock();
|
||||
node.handler = null;
|
||||
if (token.type === tt.catch) {
|
||||
if (token.type === tt._catch) {
|
||||
var clause = startNode();
|
||||
next();
|
||||
expect(tt.parenL);
|
||||
@ -396,23 +394,23 @@
|
||||
clause.body = parseBlock();
|
||||
node.handler = finishNode(clause, "CatchClause");
|
||||
}
|
||||
node.finalizer = eat(tt.finally) ? parseBlock() : null;
|
||||
node.finalizer = eat(tt._finally) ? parseBlock() : null;
|
||||
if (!node.handler && !node.finalizer) return node.block;
|
||||
return finishNode(node, "TryStatement");
|
||||
|
||||
case tt.var:
|
||||
case tt._var:
|
||||
next();
|
||||
node = parseVar(node);
|
||||
semicolon();
|
||||
return node;
|
||||
|
||||
case tt.while:
|
||||
case tt._while:
|
||||
next();
|
||||
node.test = parseParenExpression();
|
||||
node.body = parseStatement();
|
||||
return finishNode(node, "WhileStatement");
|
||||
|
||||
case tt.with:
|
||||
case tt._with:
|
||||
next();
|
||||
node.object = parseParenExpression();
|
||||
node.body = parseStatement();
|
||||
@ -426,7 +424,7 @@
|
||||
return finishNode(node, "EmptyStatement");
|
||||
|
||||
default:
|
||||
var maybeName = token.value, expr = parseExpression();
|
||||
var expr = parseExpression();
|
||||
if (isDummy(expr)) {
|
||||
next();
|
||||
if (token.type === tt.eof) return finishNode(node, "EmptyStatement");
|
||||
@ -449,7 +447,7 @@
|
||||
expect(tt.braceL);
|
||||
var blockIndent = curIndent, line = curLineStart;
|
||||
node.body = [];
|
||||
while (!closesBlock(tt.braceR, blockIndent, line))
|
||||
while (!closes(tt.braceR, blockIndent, line, true))
|
||||
node.body.push(parseStatement());
|
||||
popCx();
|
||||
eat(tt.braceR);
|
||||
@ -486,6 +484,11 @@
|
||||
node.declarations.push(finishNode(decl, "VariableDeclarator"));
|
||||
if (!eat(tt.comma)) break;
|
||||
}
|
||||
if (!node.declarations.length) {
|
||||
var decl = startNode();
|
||||
decl.id = dummyIdent();
|
||||
node.declarations.push(finishNode(decl, "VariableDeclarator"));
|
||||
}
|
||||
return finishNode(node, "VariableDeclaration");
|
||||
}
|
||||
|
||||
@ -542,7 +545,7 @@
|
||||
function parseExprOp(left, minPrec, noIn, indent, line) {
|
||||
if (curLineStart != line && curIndent < indent && tokenStartsLine()) return left;
|
||||
var prec = token.type.binop;
|
||||
if (prec != null && (!noIn || token.type !== tt.in)) {
|
||||
if (prec != null && (!noIn || token.type !== tt._in)) {
|
||||
if (prec > minPrec) {
|
||||
var node = startNodeFrom(left);
|
||||
node.left = left;
|
||||
@ -582,8 +585,7 @@
|
||||
}
|
||||
|
||||
function parseExprSubscripts() {
|
||||
var indent = curIndent, line = curLineStart;
|
||||
return parseSubscripts(parseExprAtom(), false, curIndent, line);
|
||||
return parseSubscripts(parseExprAtom(), false, curIndent, curLineStart);
|
||||
}
|
||||
|
||||
function parseSubscripts(base, noCalls, startIndent, line) {
|
||||
@ -628,7 +630,7 @@
|
||||
|
||||
function parseExprAtom() {
|
||||
switch (token.type) {
|
||||
case tt.this:
|
||||
case tt._this:
|
||||
var node = startNode();
|
||||
next();
|
||||
return finishNode(node, "ThisExpression");
|
||||
@ -641,10 +643,10 @@
|
||||
next();
|
||||
return finishNode(node, "Literal");
|
||||
|
||||
case tt.null: case tt.true: case tt.false:
|
||||
case tt._null: case tt._true: case tt._false:
|
||||
var node = startNode();
|
||||
node.value = token.type.atomValue;
|
||||
node.raw = token.type.keyword
|
||||
node.raw = token.type.keyword;
|
||||
next();
|
||||
return finishNode(node, "Literal");
|
||||
|
||||
@ -666,12 +668,12 @@
|
||||
case tt.braceL:
|
||||
return parseObj();
|
||||
|
||||
case tt.function:
|
||||
case tt._function:
|
||||
var node = startNode();
|
||||
next();
|
||||
return parseFunction(node, false);
|
||||
|
||||
case tt.new:
|
||||
case tt._new:
|
||||
return parseNew();
|
||||
|
||||
default:
|
||||
@ -698,7 +700,7 @@
|
||||
pushCx();
|
||||
next();
|
||||
var propIndent = curIndent, line = curLineStart;
|
||||
while (!closesBlock(tt.braceR, propIndent, line)) {
|
||||
while (!closes(tt.braceR, propIndent, line)) {
|
||||
var name = parsePropertyName();
|
||||
if (!name) { if (isDummy(parseExpression(true))) next(); eat(tt.comma); continue; }
|
||||
var prop = {key: name}, isGetSet = false, kind;
|
||||
@ -755,12 +757,13 @@
|
||||
}
|
||||
|
||||
function parseExprList(close) {
|
||||
var indent = curIndent + 1, line = curLineStart, elts = [];
|
||||
var indent = curIndent, line = curLineStart, elts = [], continuedLine = nextLineStart;
|
||||
next(); // Opening bracket
|
||||
while (!closesBlock(close, indent, line)) {
|
||||
if (curLineStart > continuedLine) continuedLine = curLineStart;
|
||||
while (!closes(close, indent + (curLineStart <= continuedLine ? 1 : 0), line)) {
|
||||
var elt = parseExpression(true);
|
||||
if (isDummy(elt)) {
|
||||
if (closesBlock(close, indent, line)) break;
|
||||
if (closes(close, indent, line)) break;
|
||||
next();
|
||||
} else {
|
||||
elts.push(elt);
|
||||
|
@ -12,7 +12,6 @@ const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const MAX_ITERATIONS = 100;
|
||||
const REGEX_QUOTES = /^".*?"|^".*|^'.*?'|^'.*/;
|
||||
const REGEX_URL = /^url\(["']?(.+?)(?::(\d+))?["']?\)/;
|
||||
const REGEX_WHITESPACE = /^\s+/;
|
||||
const REGEX_FIRST_WORD_OR_CHAR = /^\w+|^./;
|
||||
const REGEX_CSS_PROPERTY_VALUE = /(^[^;]+)/;
|
||||
@ -119,6 +118,32 @@ OutputParser.prototype = {
|
||||
return this._parse(value, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Matches the beginning of the provided string to a css background-image url
|
||||
* and return both the whole url(...) match and the url itself.
|
||||
* This isn't handled via a regular expression to make sure we can match urls
|
||||
* that contain parenthesis easily
|
||||
*/
|
||||
_matchBackgroundUrl: function(text) {
|
||||
let startToken = "url(";
|
||||
if (text.indexOf(startToken) !== 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let uri = text.substring(startToken.length).trim();
|
||||
let quote = uri.substring(0, 1);
|
||||
if (quote === "'" || quote === '"') {
|
||||
uri = uri.substring(1, uri.search(new RegExp(quote + "\\s*\\)")));
|
||||
} else {
|
||||
uri = uri.substring(0, uri.indexOf(")"));
|
||||
quote = "";
|
||||
}
|
||||
let end = startToken + quote + uri;
|
||||
text = text.substring(0, text.indexOf(")", end.length) + 1);
|
||||
|
||||
return [text, uri.trim()];
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse a string.
|
||||
*
|
||||
@ -166,11 +191,11 @@ OutputParser.prototype = {
|
||||
continue;
|
||||
}
|
||||
|
||||
matched = text.match(REGEX_URL);
|
||||
matched = this._matchBackgroundUrl(text);
|
||||
if (matched) {
|
||||
let [match, url] = matched;
|
||||
|
||||
text = this._trimMatchFromStart(text, match);
|
||||
|
||||
this._appendURL(match, url, options);
|
||||
continue;
|
||||
}
|
||||
|
@ -65,6 +65,8 @@ const {HighlighterActor} = require("devtools/server/actors/highlighter");
|
||||
|
||||
const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
|
||||
const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
|
||||
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const IMAGE_FETCHING_TIMEOUT = 500;
|
||||
// The possible completions to a ':' with added score to give certain values
|
||||
// some preference.
|
||||
const PSEUDO_SELECTORS = [
|
||||
@ -288,58 +290,25 @@ var NodeActor = exports.NodeActor = protocol.ActorClass({
|
||||
|
||||
/**
|
||||
* Get the node's image data if any (for canvas and img nodes).
|
||||
* Returns a LongStringActor with the image or canvas' image data as png
|
||||
* a data:image/png;base64,.... string
|
||||
* A null return value means the node isn't an image
|
||||
* An empty string return value means the node is an image but image data
|
||||
* could not be retrieved (missing/broken image).
|
||||
* Returns an imageData object with the actual data being a LongStringActor
|
||||
* and a size json object.
|
||||
* The image data is transmitted as a base64 encoded png data-uri.
|
||||
* The method rejects if the node isn't an image or if the image is missing
|
||||
*
|
||||
* Accepts a maxDim request parameter to resize images that are larger. This
|
||||
* is important as the resizing occurs server-side so that image-data being
|
||||
* transfered in the longstring back to the client will be that much smaller
|
||||
*/
|
||||
getImageData: method(function(maxDim) {
|
||||
let isImg = this.rawNode.tagName.toLowerCase() === "img";
|
||||
let isCanvas = this.rawNode.tagName.toLowerCase() === "canvas";
|
||||
|
||||
if (!isImg && !isCanvas) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the image resize ratio if a maxDim was provided
|
||||
let resizeRatio = 1;
|
||||
let imgWidth = isImg ? this.rawNode.naturalWidth : this.rawNode.width;
|
||||
let imgHeight = isImg ? this.rawNode.naturalHeight : this.rawNode.height;
|
||||
let imgMax = Math.max(imgWidth, imgHeight);
|
||||
if (maxDim && imgMax > maxDim) {
|
||||
resizeRatio = maxDim / imgMax;
|
||||
}
|
||||
|
||||
// Create a canvas to copy the rawNode into and get the imageData from
|
||||
let canvas = this.rawNode.ownerDocument.createElement("canvas");
|
||||
canvas.width = imgWidth * resizeRatio;
|
||||
canvas.height = imgHeight * resizeRatio;
|
||||
let ctx = canvas.getContext("2d");
|
||||
|
||||
// Copy the rawNode image or canvas in the new canvas and extract data
|
||||
let imageData;
|
||||
// This may fail if the image is missing
|
||||
// imageToImageData may fail if the node isn't an image
|
||||
try {
|
||||
ctx.drawImage(this.rawNode, 0, 0, canvas.width, canvas.height);
|
||||
imageData = canvas.toDataURL("image/png");
|
||||
} catch (e) {
|
||||
imageData = "";
|
||||
}
|
||||
|
||||
return {
|
||||
data: LongStringActor(this.conn, imageData),
|
||||
size: {
|
||||
naturalWidth: imgWidth,
|
||||
naturalHeight: imgHeight,
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
resized: resizeRatio !== 1
|
||||
}
|
||||
let imageData = imageToImageData(this.rawNode, maxDim);
|
||||
return promise.resolve({
|
||||
data: LongStringActor(this.conn, imageData.data),
|
||||
size: imageData.size
|
||||
});
|
||||
} catch(e) {
|
||||
return promise.reject(new Error("Image not available"));
|
||||
}
|
||||
}, {
|
||||
request: {maxDim: Arg(0, "nullable:number")},
|
||||
@ -1362,8 +1331,7 @@ var WalkerActor = protocol.ActorClass({
|
||||
* Helper function for the `children` method: Read forward in the sibling
|
||||
* list into an array with `count` items, including the current node.
|
||||
*/
|
||||
_readForward: function(walker, count)
|
||||
{
|
||||
_readForward: function(walker, count) {
|
||||
let ret = [];
|
||||
let node = walker.currentNode;
|
||||
do {
|
||||
@ -1377,8 +1345,7 @@ var WalkerActor = protocol.ActorClass({
|
||||
* Helper function for the `children` method: Read backward in the sibling
|
||||
* list into an array with `count` items, including the current node.
|
||||
*/
|
||||
_readBackward: function(walker, count)
|
||||
{
|
||||
_readBackward: function(walker, count) {
|
||||
let ret = [];
|
||||
let node = walker.currentNode;
|
||||
do {
|
||||
@ -2403,7 +2370,7 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
|
||||
|
||||
// XXX hack during transition to remote inspector: get a proper NodeFront
|
||||
// for a given local node. Only works locally.
|
||||
frontForRawNode: function(rawNode){
|
||||
frontForRawNode: function(rawNode) {
|
||||
if (!this.isLocal()) {
|
||||
console.warn("Tried to use frontForRawNode on a remote connection.");
|
||||
return null;
|
||||
@ -2550,6 +2517,54 @@ var InspectorActor = protocol.ActorClass({
|
||||
response: {
|
||||
highligter: RetVal("highlighter")
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get the node's image data if any (for canvas and img nodes).
|
||||
* Returns an imageData object with the actual data being a LongStringActor
|
||||
* and a size json object.
|
||||
* The image data is transmitted as a base64 encoded png data-uri.
|
||||
* The method rejects if the node isn't an image or if the image is missing
|
||||
*
|
||||
* Accepts a maxDim request parameter to resize images that are larger. This
|
||||
* is important as the resizing occurs server-side so that image-data being
|
||||
* transfered in the longstring back to the client will be that much smaller
|
||||
*/
|
||||
getImageDataFromURL: method(function(url, maxDim) {
|
||||
let deferred = promise.defer();
|
||||
let img = new this.window.Image();
|
||||
|
||||
// On load, get the image data and send the response
|
||||
img.onload = () => {
|
||||
// imageToImageData throws an error if the image is missing
|
||||
try {
|
||||
let imageData = imageToImageData(img, maxDim);
|
||||
deferred.resolve({
|
||||
data: LongStringActor(this.conn, imageData.data),
|
||||
size: imageData.size
|
||||
});
|
||||
} catch (e) {
|
||||
deferred.reject(new Error("Image " + url+ " not available"));
|
||||
}
|
||||
}
|
||||
|
||||
// If the URL doesn't point to a resource, reject
|
||||
img.onerror = () => {
|
||||
deferred.reject(new Error("Image " + url+ " not available"));
|
||||
}
|
||||
|
||||
// If the request hangs for too long, kill it to avoid queuing up other requests
|
||||
// to the same actor
|
||||
this.window.setTimeout(() => {
|
||||
deferred.reject(new Error("Image " + url + " could not be retrieved in time"));
|
||||
}, IMAGE_FETCHING_TIMEOUT);
|
||||
|
||||
img.src = url;
|
||||
|
||||
return deferred.promise;
|
||||
}, {
|
||||
request: {url: Arg(0), maxDim: Arg(1, "nullable:number")},
|
||||
response: RetVal("imageData")
|
||||
})
|
||||
});
|
||||
|
||||
@ -2615,8 +2630,7 @@ function nodeDocument(node) {
|
||||
*
|
||||
* See TreeWalker documentation for explanations of the methods.
|
||||
*/
|
||||
function DocumentWalker(aNode, aRootWin, aShow, aFilter, aExpandEntityReferences)
|
||||
{
|
||||
function DocumentWalker(aNode, aRootWin, aShow, aFilter, aExpandEntityReferences) {
|
||||
let doc = nodeDocument(aNode);
|
||||
this.layoutHelpers = new LayoutHelpers(aRootWin);
|
||||
this.walker = doc.createTreeWalker(doc,
|
||||
@ -2637,7 +2651,7 @@ DocumentWalker.prototype = {
|
||||
* the current node, creates a new treewalker for the document we've
|
||||
* run in to.
|
||||
*/
|
||||
_reparentWalker: function DW_reparentWalker(aNewNode) {
|
||||
_reparentWalker: function(aNewNode) {
|
||||
if (!aNewNode) {
|
||||
return null;
|
||||
}
|
||||
@ -2649,8 +2663,7 @@ DocumentWalker.prototype = {
|
||||
return aNewNode;
|
||||
},
|
||||
|
||||
parentNode: function DW_parentNode()
|
||||
{
|
||||
parentNode: function() {
|
||||
let currentNode = this.walker.currentNode;
|
||||
let parentNode = this.walker.parentNode();
|
||||
|
||||
@ -2670,8 +2683,7 @@ DocumentWalker.prototype = {
|
||||
return parentNode;
|
||||
},
|
||||
|
||||
firstChild: function DW_firstChild()
|
||||
{
|
||||
firstChild: function() {
|
||||
let node = this.walker.currentNode;
|
||||
if (!node)
|
||||
return null;
|
||||
@ -2683,8 +2695,7 @@ DocumentWalker.prototype = {
|
||||
return this.walker.firstChild();
|
||||
},
|
||||
|
||||
lastChild: function DW_lastChild()
|
||||
{
|
||||
lastChild: function() {
|
||||
let node = this.walker.currentNode;
|
||||
if (!node)
|
||||
return null;
|
||||
@ -2698,13 +2709,12 @@ DocumentWalker.prototype = {
|
||||
|
||||
previousSibling: function DW_previousSibling() this.walker.previousSibling(),
|
||||
nextSibling: function DW_nextSibling() this.walker.nextSibling()
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A tree walker filter for avoiding empty whitespace text nodes.
|
||||
*/
|
||||
function whitespaceTextFilter(aNode)
|
||||
{
|
||||
function whitespaceTextFilter(aNode) {
|
||||
if (aNode.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
|
||||
!/[^\s]/.exec(aNode.nodeValue)) {
|
||||
return Ci.nsIDOMNodeFilter.FILTER_SKIP;
|
||||
@ -2713,6 +2723,60 @@ function whitespaceTextFilter(aNode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an image DOMNode, return the image data-uri.
|
||||
* @param {DOMNode} node The image node
|
||||
* @param {Number} maxDim Optionally pass a maximum size you want the longest
|
||||
* side of the image to be resized to before getting the image data.
|
||||
* @return {Object} An object containing the data-uri and size-related information
|
||||
* {data: "...", size: {naturalWidth: 400, naturalHeight: 300, resized: true}}
|
||||
* @throws an error if the node isn't an image or if the image is missing
|
||||
*/
|
||||
function imageToImageData(node, maxDim) {
|
||||
let isImg = node.tagName.toLowerCase() === "img";
|
||||
let isCanvas = node.tagName.toLowerCase() === "canvas";
|
||||
|
||||
if (!isImg && !isCanvas) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the image resize ratio if a maxDim was provided
|
||||
let resizeRatio = 1;
|
||||
let imgWidth = node.naturalWidth || node.width;
|
||||
let imgHeight = node.naturalHeight || node.height;
|
||||
let imgMax = Math.max(imgWidth, imgHeight);
|
||||
if (maxDim && imgMax > maxDim) {
|
||||
resizeRatio = maxDim / imgMax;
|
||||
}
|
||||
|
||||
// Extract the image data
|
||||
let imageData;
|
||||
// The image may already be a data-uri, in which case, save ourselves the
|
||||
// trouble of converting via the canvas.drawImage.toDataURL method
|
||||
if (isImg && node.src.startsWith("data:")) {
|
||||
imageData = node.src;
|
||||
} else {
|
||||
// Create a canvas to copy the rawNode into and get the imageData from
|
||||
let canvas = node.ownerDocument.createElementNS(XHTML_NS, "canvas");
|
||||
canvas.width = imgWidth * resizeRatio;
|
||||
canvas.height = imgHeight * resizeRatio;
|
||||
let ctx = canvas.getContext("2d");
|
||||
|
||||
// Copy the rawNode image or canvas in the new canvas and extract data
|
||||
ctx.drawImage(node, 0, 0, canvas.width, canvas.height);
|
||||
imageData = canvas.toDataURL("image/png");
|
||||
}
|
||||
|
||||
return {
|
||||
data: imageData,
|
||||
size: {
|
||||
naturalWidth: imgWidth,
|
||||
naturalHeight: imgHeight,
|
||||
resized: resizeRatio !== 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loader.lazyGetter(this, "DOMUtils", function () {
|
||||
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||
});
|
||||
|
@ -179,7 +179,11 @@ RootActor.prototype = {
|
||||
editOuterHTML: true,
|
||||
// Wether the server-side highlighter actor exists and can be used to
|
||||
// remotely highlight nodes (see server/actors/highlighter.js)
|
||||
highlightable: true
|
||||
highlightable: true,
|
||||
// Wether the inspector actor implements the getImageDataFromURL
|
||||
// method that returns data-uris for image URLs. This is used for image
|
||||
// tooltips for instance
|
||||
urlToImageDataResolver: true
|
||||
}
|
||||
};
|
||||
},
|
||||
|
@ -46,8 +46,6 @@ addTest(function testLargeImage() {
|
||||
ok(imageData.size, "Image size info was sent back too");
|
||||
is(imageData.size.naturalWidth, 5333, "Natural width of the image correct");
|
||||
is(imageData.size.naturalHeight, 3000, "Natural width of the image correct");
|
||||
is(imageData.size.width, 100, "Resized image width correct");
|
||||
is(imageData.size.height, 56, "Resized image height correct");
|
||||
ok(imageData.size.resized, "Image was resized");
|
||||
|
||||
imageData.data.string().then(str => {
|
||||
@ -69,8 +67,6 @@ addTest(function testLargeCanvas() {
|
||||
ok(imageData.size, "Image size info was sent back too");
|
||||
is(imageData.size.naturalWidth, 1000, "Natural width of the image correct");
|
||||
is(imageData.size.naturalHeight, 2000, "Natural width of the image correct");
|
||||
is(imageData.size.width, 175, "Resized image width correct");
|
||||
is(imageData.size.height, 350, "Resized image height correct");
|
||||
ok(imageData.size.resized, "Image was resized");
|
||||
|
||||
imageData.data.string().then(str => {
|
||||
@ -92,8 +88,6 @@ addTest(function testSmallImage() {
|
||||
ok(imageData.size, "Image size info was sent back too");
|
||||
is(imageData.size.naturalWidth, 245, "Natural width of the image correct");
|
||||
is(imageData.size.naturalHeight, 240, "Natural width of the image correct");
|
||||
is(imageData.size.width, 245, "Resized image width correct");
|
||||
is(imageData.size.height, 240, "Resized image height correct");
|
||||
ok(!imageData.size.resized, "Image was NOT resized");
|
||||
|
||||
imageData.data.string().then(str => {
|
||||
|
@ -809,6 +809,42 @@ CssLogic.shortSource = function CssLogic_shortSource(aSheet)
|
||||
return dataUrl ? dataUrl[1] : aSheet.href;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the background image URL (if any) from a property value.
|
||||
* Used, for example, for the preview tooltip in the rule view and
|
||||
* computed view.
|
||||
*
|
||||
* @param {String} aProperty
|
||||
* @param {String} aSheetHref
|
||||
* @return {string} a image URL
|
||||
*/
|
||||
CssLogic.getBackgroundImageUriFromProperty = function(aProperty, aSheetHref) {
|
||||
let startToken = "url(", start = aProperty.indexOf(startToken), end;
|
||||
if (start === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
aProperty = aProperty.substring(start + startToken.length).trim();
|
||||
let quote = aProperty.substring(0, 1);
|
||||
if (quote === "'" || quote === '"') {
|
||||
end = aProperty.search(new RegExp(quote + "\\s*\\)"));
|
||||
start = 1;
|
||||
} else {
|
||||
end = aProperty.indexOf(")");
|
||||
start = 0;
|
||||
}
|
||||
|
||||
let uri = aProperty.substring(start, end).trim();
|
||||
if (aSheetHref) {
|
||||
let IOService = Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService);
|
||||
let sheetUri = IOService.newURI(aSheetHref, null, null);
|
||||
uri = sheetUri.resolve(uri);
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the position of [element] in [nodeList].
|
||||
* @returns an index of the match, or -1 if there is no match
|
||||
|
@ -75,7 +75,7 @@ panel[type="arrow"][side="right"] {
|
||||
}
|
||||
|
||||
.panel-arrowcontent {
|
||||
padding: 10px;
|
||||
padding: 4px;
|
||||
color: -moz-FieldText;
|
||||
background: -moz-field;
|
||||
background-clip: padding-box;
|
||||
|
@ -3695,70 +3695,6 @@ bool nsWindow::DispatchWindowEvent(WidgetGUIEvent* event,
|
||||
return ConvertStatus(aStatus);
|
||||
}
|
||||
|
||||
bool nsWindow::DispatchCommandEvent(uint32_t aEventCommand)
|
||||
{
|
||||
nsCOMPtr<nsIAtom> command;
|
||||
switch (aEventCommand) {
|
||||
case APPCOMMAND_BROWSER_BACKWARD:
|
||||
command = nsGkAtoms::Back;
|
||||
break;
|
||||
case APPCOMMAND_BROWSER_FORWARD:
|
||||
command = nsGkAtoms::Forward;
|
||||
break;
|
||||
case APPCOMMAND_BROWSER_REFRESH:
|
||||
command = nsGkAtoms::Reload;
|
||||
break;
|
||||
case APPCOMMAND_BROWSER_STOP:
|
||||
command = nsGkAtoms::Stop;
|
||||
break;
|
||||
case APPCOMMAND_BROWSER_SEARCH:
|
||||
command = nsGkAtoms::Search;
|
||||
break;
|
||||
case APPCOMMAND_BROWSER_FAVORITES:
|
||||
command = nsGkAtoms::Bookmarks;
|
||||
break;
|
||||
case APPCOMMAND_BROWSER_HOME:
|
||||
command = nsGkAtoms::Home;
|
||||
break;
|
||||
case APPCOMMAND_CLOSE:
|
||||
command = nsGkAtoms::Close;
|
||||
break;
|
||||
case APPCOMMAND_FIND:
|
||||
command = nsGkAtoms::Find;
|
||||
break;
|
||||
case APPCOMMAND_HELP:
|
||||
command = nsGkAtoms::Help;
|
||||
break;
|
||||
case APPCOMMAND_NEW:
|
||||
command = nsGkAtoms::New;
|
||||
break;
|
||||
case APPCOMMAND_OPEN:
|
||||
command = nsGkAtoms::Open;
|
||||
break;
|
||||
case APPCOMMAND_PRINT:
|
||||
command = nsGkAtoms::Print;
|
||||
break;
|
||||
case APPCOMMAND_SAVE:
|
||||
command = nsGkAtoms::Save;
|
||||
break;
|
||||
case APPCOMMAND_FORWARD_MAIL:
|
||||
command = nsGkAtoms::ForwardMail;
|
||||
break;
|
||||
case APPCOMMAND_REPLY_TO_MAIL:
|
||||
command = nsGkAtoms::ReplyToMail;
|
||||
break;
|
||||
case APPCOMMAND_SEND_MAIL:
|
||||
command = nsGkAtoms::SendMail;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
WidgetCommandEvent event(true, nsGkAtoms::onAppCommand, command, this);
|
||||
|
||||
InitEvent(event);
|
||||
return DispatchWindowEvent(&event);
|
||||
}
|
||||
|
||||
// Recursively dispatch synchronous paints for nsIWidget
|
||||
// descendants with invalidated rectangles.
|
||||
BOOL CALLBACK nsWindow::DispatchStarvedPaints(HWND aWnd, LPARAM aMsg)
|
||||
@ -5089,69 +5025,8 @@ nsWindow::ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam,
|
||||
break;
|
||||
|
||||
case WM_APPCOMMAND:
|
||||
{
|
||||
uint32_t appCommand = GET_APPCOMMAND_LPARAM(lParam);
|
||||
uint32_t contentCommandMessage = NS_EVENT_NULL;
|
||||
// XXX After we implement KeyboardEvent.key, we should dispatch the
|
||||
// key event if (GET_DEVICE_LPARAM(lParam) == FAPPCOMMAND_KEY) is.
|
||||
switch (appCommand)
|
||||
{
|
||||
case APPCOMMAND_BROWSER_BACKWARD:
|
||||
case APPCOMMAND_BROWSER_FORWARD:
|
||||
case APPCOMMAND_BROWSER_REFRESH:
|
||||
case APPCOMMAND_BROWSER_STOP:
|
||||
case APPCOMMAND_BROWSER_SEARCH:
|
||||
case APPCOMMAND_BROWSER_FAVORITES:
|
||||
case APPCOMMAND_BROWSER_HOME:
|
||||
case APPCOMMAND_CLOSE:
|
||||
case APPCOMMAND_FIND:
|
||||
case APPCOMMAND_HELP:
|
||||
case APPCOMMAND_NEW:
|
||||
case APPCOMMAND_OPEN:
|
||||
case APPCOMMAND_PRINT:
|
||||
case APPCOMMAND_SAVE:
|
||||
case APPCOMMAND_FORWARD_MAIL:
|
||||
case APPCOMMAND_REPLY_TO_MAIL:
|
||||
case APPCOMMAND_SEND_MAIL:
|
||||
// We shouldn't consume the message always because if we don't handle
|
||||
// the message, the sender (typically, utility of keyboard or mouse)
|
||||
// may send other key messages which indicate well known shortcut key.
|
||||
if (DispatchCommandEvent(appCommand)) {
|
||||
// tell the driver that we handled the event
|
||||
*aRetValue = 1;
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
|
||||
// Use content command for following commands:
|
||||
case APPCOMMAND_COPY:
|
||||
contentCommandMessage = NS_CONTENT_COMMAND_COPY;
|
||||
break;
|
||||
case APPCOMMAND_CUT:
|
||||
contentCommandMessage = NS_CONTENT_COMMAND_CUT;
|
||||
break;
|
||||
case APPCOMMAND_PASTE:
|
||||
contentCommandMessage = NS_CONTENT_COMMAND_PASTE;
|
||||
break;
|
||||
case APPCOMMAND_REDO:
|
||||
contentCommandMessage = NS_CONTENT_COMMAND_REDO;
|
||||
break;
|
||||
case APPCOMMAND_UNDO:
|
||||
contentCommandMessage = NS_CONTENT_COMMAND_UNDO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (contentCommandMessage) {
|
||||
WidgetContentCommandEvent contentCommand(true, contentCommandMessage,
|
||||
this);
|
||||
DispatchWindowEvent(&contentCommand);
|
||||
// tell the driver that we handled the event
|
||||
*aRetValue = 1;
|
||||
result = true;
|
||||
}
|
||||
// default = false - tell the driver that the event was not handled
|
||||
}
|
||||
break;
|
||||
result = HandleAppCommandMsg(wParam, lParam, aRetValue);
|
||||
break;
|
||||
|
||||
// The WM_ACTIVATE event is fired when a window is raised or lowered,
|
||||
// and the loword of wParam specifies which. But we don't want to tell
|
||||
|
@ -357,7 +357,6 @@ protected:
|
||||
*/
|
||||
void DispatchFocusToTopLevelWindow(bool aIsActivate);
|
||||
bool DispatchStandardEvent(uint32_t aMsg);
|
||||
bool DispatchCommandEvent(uint32_t aEventCommand);
|
||||
void RelayMouseEvent(UINT aMsg, WPARAM wParam, LPARAM lParam);
|
||||
virtual bool ProcessMessage(UINT msg, WPARAM &wParam,
|
||||
LPARAM &lParam, LRESULT *aRetValue);
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include "nsWindowBase.h"
|
||||
|
||||
#include "mozilla/MiscEvents.h"
|
||||
|
||||
#include "nsGkAtoms.h"
|
||||
#include "WinUtils.h"
|
||||
#include "npapi.h"
|
||||
|
||||
@ -194,4 +194,136 @@ nsWindowBase::ClearNativeTouchSequence()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
nsWindowBase::DispatchCommandEvent(uint32_t aEventCommand)
|
||||
{
|
||||
nsCOMPtr<nsIAtom> command;
|
||||
switch (aEventCommand) {
|
||||
case APPCOMMAND_BROWSER_BACKWARD:
|
||||
command = nsGkAtoms::Back;
|
||||
break;
|
||||
case APPCOMMAND_BROWSER_FORWARD:
|
||||
command = nsGkAtoms::Forward;
|
||||
break;
|
||||
case APPCOMMAND_BROWSER_REFRESH:
|
||||
command = nsGkAtoms::Reload;
|
||||
break;
|
||||
case APPCOMMAND_BROWSER_STOP:
|
||||
command = nsGkAtoms::Stop;
|
||||
break;
|
||||
case APPCOMMAND_BROWSER_SEARCH:
|
||||
command = nsGkAtoms::Search;
|
||||
break;
|
||||
case APPCOMMAND_BROWSER_FAVORITES:
|
||||
command = nsGkAtoms::Bookmarks;
|
||||
break;
|
||||
case APPCOMMAND_BROWSER_HOME:
|
||||
command = nsGkAtoms::Home;
|
||||
break;
|
||||
case APPCOMMAND_CLOSE:
|
||||
command = nsGkAtoms::Close;
|
||||
break;
|
||||
case APPCOMMAND_FIND:
|
||||
command = nsGkAtoms::Find;
|
||||
break;
|
||||
case APPCOMMAND_HELP:
|
||||
command = nsGkAtoms::Help;
|
||||
break;
|
||||
case APPCOMMAND_NEW:
|
||||
command = nsGkAtoms::New;
|
||||
break;
|
||||
case APPCOMMAND_OPEN:
|
||||
command = nsGkAtoms::Open;
|
||||
break;
|
||||
case APPCOMMAND_PRINT:
|
||||
command = nsGkAtoms::Print;
|
||||
break;
|
||||
case APPCOMMAND_SAVE:
|
||||
command = nsGkAtoms::Save;
|
||||
break;
|
||||
case APPCOMMAND_FORWARD_MAIL:
|
||||
command = nsGkAtoms::ForwardMail;
|
||||
break;
|
||||
case APPCOMMAND_REPLY_TO_MAIL:
|
||||
command = nsGkAtoms::ReplyToMail;
|
||||
break;
|
||||
case APPCOMMAND_SEND_MAIL:
|
||||
command = nsGkAtoms::SendMail;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
WidgetCommandEvent event(true, nsGkAtoms::onAppCommand, command, this);
|
||||
|
||||
InitEvent(event);
|
||||
return DispatchWindowEvent(&event);
|
||||
}
|
||||
|
||||
bool
|
||||
nsWindowBase::HandleAppCommandMsg(WPARAM aWParam,
|
||||
LPARAM aLParam,
|
||||
LRESULT *aRetValue)
|
||||
{
|
||||
uint32_t appCommand = GET_APPCOMMAND_LPARAM(aLParam);
|
||||
uint32_t contentCommandMessage = NS_EVENT_NULL;
|
||||
// XXX After we implement KeyboardEvent.key, we should dispatch the
|
||||
// key event if (GET_DEVICE_LPARAM(lParam) == FAPPCOMMAND_KEY) is.
|
||||
switch (appCommand)
|
||||
{
|
||||
case APPCOMMAND_BROWSER_BACKWARD:
|
||||
case APPCOMMAND_BROWSER_FORWARD:
|
||||
case APPCOMMAND_BROWSER_REFRESH:
|
||||
case APPCOMMAND_BROWSER_STOP:
|
||||
case APPCOMMAND_BROWSER_SEARCH:
|
||||
case APPCOMMAND_BROWSER_FAVORITES:
|
||||
case APPCOMMAND_BROWSER_HOME:
|
||||
case APPCOMMAND_CLOSE:
|
||||
case APPCOMMAND_FIND:
|
||||
case APPCOMMAND_HELP:
|
||||
case APPCOMMAND_NEW:
|
||||
case APPCOMMAND_OPEN:
|
||||
case APPCOMMAND_PRINT:
|
||||
case APPCOMMAND_SAVE:
|
||||
case APPCOMMAND_FORWARD_MAIL:
|
||||
case APPCOMMAND_REPLY_TO_MAIL:
|
||||
case APPCOMMAND_SEND_MAIL:
|
||||
// We shouldn't consume the message always because if we don't handle
|
||||
// the message, the sender (typically, utility of keyboard or mouse)
|
||||
// may send other key messages which indicate well known shortcut key.
|
||||
if (DispatchCommandEvent(appCommand)) {
|
||||
// tell the driver that we handled the event
|
||||
*aRetValue = 1;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
// Use content command for following commands:
|
||||
case APPCOMMAND_COPY:
|
||||
contentCommandMessage = NS_CONTENT_COMMAND_COPY;
|
||||
break;
|
||||
case APPCOMMAND_CUT:
|
||||
contentCommandMessage = NS_CONTENT_COMMAND_CUT;
|
||||
break;
|
||||
case APPCOMMAND_PASTE:
|
||||
contentCommandMessage = NS_CONTENT_COMMAND_PASTE;
|
||||
break;
|
||||
case APPCOMMAND_REDO:
|
||||
contentCommandMessage = NS_CONTENT_COMMAND_REDO;
|
||||
break;
|
||||
case APPCOMMAND_UNDO:
|
||||
contentCommandMessage = NS_CONTENT_COMMAND_UNDO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (contentCommandMessage) {
|
||||
WidgetContentCommandEvent contentCommand(true, contentCommandMessage,
|
||||
this);
|
||||
DispatchWindowEvent(&contentCommand);
|
||||
// tell the driver that we handled the event
|
||||
*aRetValue = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
// default = false - tell the driver that the event was not handled
|
||||
return false;
|
||||
}
|
||||
|
@ -78,7 +78,6 @@ public:
|
||||
return (mInputContext.mIMEState.mEnabled == IMEState::PLUGIN);
|
||||
}
|
||||
|
||||
public:
|
||||
/*
|
||||
* Touch input injection apis
|
||||
*/
|
||||
@ -89,7 +88,15 @@ public:
|
||||
uint32_t aPointerOrientation);
|
||||
virtual nsresult ClearNativeTouchSequence();
|
||||
|
||||
/*
|
||||
* WM_APPCOMMAND common handler. Sends events via DispatchWindowEvent.
|
||||
*/
|
||||
virtual bool HandleAppCommandMsg(WPARAM aWParam,
|
||||
LPARAM aLParam,
|
||||
LRESULT *aRetValue);
|
||||
|
||||
protected:
|
||||
bool DispatchCommandEvent(uint32_t aEventCommand);
|
||||
static bool InitTouchInjection();
|
||||
bool InjectTouchPoint(uint32_t aId, nsIntPoint& aPointerScreenPoint,
|
||||
POINTER_FLAGS aFlags, uint32_t aPressure = 1024,
|
||||
|
@ -889,6 +889,10 @@ MetroWidget::WindowProcedure(HWND aWnd, UINT aMsg, WPARAM aWParam, LPARAM aLPara
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_APPCOMMAND:
|
||||
processDefault = HandleAppCommandMsg(aWParam, aLParam, &processResult);
|
||||
break;
|
||||
|
||||
case WM_GETOBJECT:
|
||||
{
|
||||
DWORD dwObjId = (LPARAM)(DWORD) aLParam;
|
||||
|
Loading…
Reference in New Issue
Block a user