author | Ryan VanderMeulen <ryanvm@gmail.com> |
Tue, 20 Aug 2013 16:27:00 -0400 | |
changeset 156202 | d136c8999d967cb04004373ba80420217e1a8ce4 |
parent 156176 | 6551f959f80d792b59071ea164c87585857254b5 (current diff) |
parent 156201 | ceb9333feb212fbfa040d7dffc6eda689e453563 (diff) |
child 156477 | 7e83ba2dfb5154d03ba3071c1f0476cb132b3fff |
child 156480 | bae16ff9991535fb15ee06033335f3ef91696559 |
child 156496 | 3b6752031ed33f67dd8e861d11d939da14eb015a |
child 156519 | 353b662234995befac891e3ed80724a81aab8b3a |
child 158580 | d4b94879db2603e0e96a29e0f36e5400d71fd9b5 |
push id | 2961 |
push user | lsblakk@mozilla.com |
push date | Mon, 28 Oct 2013 21:59:28 +0000 |
treeherder | mozilla-beta@73ef4f13486f [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 26.0a1 |
first release with | nightly linux32
d136c8999d96
/
26.0a1
/
20130821030213
/
files
nightly linux64
d136c8999d96
/
26.0a1
/
20130821030213
/
files
nightly mac
d136c8999d96
/
26.0a1
/
20130821030213
/
files
nightly win32
d136c8999d96
/
26.0a1
/
20130821030213
/
files
nightly win64
d136c8999d96
/
26.0a1
/
20130821030213
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
26.0a1
/
20130821030213
/
pushlog to previous
nightly linux64
26.0a1
/
20130821030213
/
pushlog to previous
nightly mac
26.0a1
/
20130821030213
/
pushlog to previous
nightly win32
26.0a1
/
20130821030213
/
pushlog to previous
nightly win64
26.0a1
/
20130821030213
/
pushlog to previous
|
toolkit/devtools/apps/tests/Makefile.in | file | annotate | diff | comparison | revisions |
--- a/browser/base/content/test/browser_tabopen_reflows.js +++ b/browser/base/content/test/browser_tabopen_reflows.js @@ -44,17 +44,21 @@ const EXPECTED_REFLOWS = [ // SessionStore.getWindowDimensions() "ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm|" + "@resource:///modules/sessionstore/SessionStore.jsm|" + "ssi_updateWindowFeatures@resource:///modules/sessionstore/SessionStore.jsm|" + "ssi_collectWindowData@resource:///modules/sessionstore/SessionStore.jsm|", // tabPreviews.capture() "tabPreviews_capture@chrome://browser/content/browser.js|" + - "tabPreviews_handleEvent/<@chrome://browser/content/browser.js|" + "tabPreviews_handleEvent/<@chrome://browser/content/browser.js|", + + // tabPreviews.capture() + "tabPreviews_capture@chrome://browser/content/browser.js|" + + "@chrome://browser/content/browser.js|" ]; const PREF_PRELOAD = "browser.newtab.preload"; /* * This test ensures that there are no unexpected * uninterruptible reflows when opening new tabs. */
--- a/browser/devtools/markupview/markup-view.js +++ b/browser/devtools/markupview/markup-view.js @@ -1214,90 +1214,111 @@ ElementEditor.prototype = { } } }, _startModifyingAttributes: function() { return this.node.startModifyingAttributes(); }, - _createAttribute: function EE_createAttribute(aAttr, aBefore) + _createAttribute: function EE_createAttribute(aAttr, aBefore = null) { - if (this.attrs.hasOwnProperty(aAttr.name)) { - var attr = this.attrs[aAttr.name]; - var name = attr.querySelector(".attrname"); - var val = attr.querySelector(".attrvalue"); - } else { - // Create the template editor, which will save some variables here. - let data = { - attrName: aAttr.name, - }; - this.template("attribute", data); - var {attr, inner, name, val} = data; + // Create the template editor, which will save some variables here. + let data = { + attrName: aAttr.name, + }; + this.template("attribute", data); + var {attr, inner, name, val} = data; - // Figure out where we should place the attribute. - let before = aBefore || null; - if (aAttr.name == "id") { - before = this.attrList.firstChild; - } else if (aAttr.name == "class") { - let idNode = this.attrs["id"]; - before = idNode ? idNode.nextSibling : this.attrList.firstChild; - } - this.attrList.insertBefore(attr, before); + // Double quotes need to be handled specially to prevent DOMParser failing. + // name="v"a"l"u"e" when editing -> name='v"a"l"u"e"' + // name="v'a"l'u"e" when editing -> name="v'a"l'u"e" + let editValueDisplayed = aAttr.value; + let hasDoubleQuote = editValueDisplayed.contains('"'); + let hasSingleQuote = editValueDisplayed.contains("'"); + let initial = aAttr.name + '="' + editValueDisplayed + '"'; + + // Can't just wrap value with ' since the value contains both " and '. + if (hasDoubleQuote && hasSingleQuote) { + editValueDisplayed = editValueDisplayed.replace(/\"/g, """); + initial = aAttr.name + '="' + editValueDisplayed + '"'; + } + + // Wrap with ' since there are no single quotes in the attribute value. + if (hasDoubleQuote && !hasSingleQuote) { + initial = aAttr.name + "='" + editValueDisplayed + "'"; + } - // Make the attribute editable. - editableField({ - element: inner, - trigger: "dblclick", - stopOnReturn: true, - selectAll: false, - contentType: InplaceEditor.CONTENT_TYPES.CSS_MIXED, - popup: this.markup.popup, - start: (aEditor, aEvent) => { - // If the editing was started inside the name or value areas, - // select accordingly. - if (aEvent && aEvent.target === name) { - aEditor.input.setSelectionRange(0, name.textContent.length); - } else if (aEvent && aEvent.target === val) { - let length = val.textContent.length; - let editorLength = aEditor.input.value.length; - let start = editorLength - (length + 1); - aEditor.input.setSelectionRange(start, start + length); - } else { - aEditor.input.select(); - } - }, - done: (aVal, aCommit) => { - if (!aCommit) { - return; - } + // Make the attribute editable. + editableField({ + element: inner, + trigger: "dblclick", + stopOnReturn: true, + selectAll: false, + initial: initial, + contentType: InplaceEditor.CONTENT_TYPES.CSS_MIXED, + popup: this.markup.popup, + start: (aEditor, aEvent) => { + // If the editing was started inside the name or value areas, + // select accordingly. + if (aEvent && aEvent.target === name) { + aEditor.input.setSelectionRange(0, name.textContent.length); + } else if (aEvent && aEvent.target === val) { + let length = editValueDisplayed.length; + let editorLength = aEditor.input.value.length; + let start = editorLength - (length + 1); + aEditor.input.setSelectionRange(start, start + length); + } else { + aEditor.input.select(); + } + }, + done: (aVal, aCommit) => { + if (!aCommit) { + return; + } + + let doMods = this._startModifyingAttributes(); + let undoMods = this._startModifyingAttributes(); - let doMods = this._startModifyingAttributes(); - let undoMods = this._startModifyingAttributes(); + // Remove the attribute stored in this editor and re-add any attributes + // parsed out of the input element. Restore original attribute if + // parsing fails. + try { + this._saveAttribute(aAttr.name, undoMods); + doMods.removeAttribute(aAttr.name); + this._applyAttributes(aVal, attr, doMods, undoMods); + this.undo.do(() => { + doMods.apply(); + }, () => { + undoMods.apply(); + }) + } catch(ex) { + console.error(ex); + } + } + }); - // Remove the attribute stored in this editor and re-add any attributes - // parsed out of the input element. Restore original attribute if - // parsing fails. - try { - this._saveAttribute(aAttr.name, undoMods); - doMods.removeAttribute(aAttr.name); - this._applyAttributes(aVal, attr, doMods, undoMods); - this.undo.do(() => { - doMods.apply(); - }, () => { - undoMods.apply(); - }) - } catch(ex) { - console.error(ex); - } - } - }); - this.attrs[aAttr.name] = attr; + // Figure out where we should place the attribute. + let before = aBefore; + if (aAttr.name == "id") { + before = this.attrList.firstChild; + } else if (aAttr.name == "class") { + let idNode = this.attrs["id"]; + before = idNode ? idNode.nextSibling : this.attrList.firstChild; } + this.attrList.insertBefore(attr, before); + + // Remove the old version of this attribute from the DOM. + let oldAttr = this.attrs[aAttr.name]; + if (oldAttr && oldAttr.parentNode) { + oldAttr.parentNode.removeChild(oldAttr); + } + + this.attrs[aAttr.name] = attr; name.textContent = aAttr.name; val.textContent = aAttr.value; return attr; }, /**
--- a/browser/devtools/markupview/test/browser_inspector_markup_edit.html +++ b/browser/devtools/markupview/test/browser_inspector_markup_edit.html @@ -35,10 +35,12 @@ </div> <div id="node22" class="unchanged"></div> <div id="node23"></div> <div id="node24"></div> <div id="retag-me"> <div id="retag-me-2"></div> </div> <div id="node25"></div> + <div id="node26" style='background-image: url("moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.org%2F");'></div> + <div id="node27" class="Double " and single '"></div> </body> </html>
--- a/browser/devtools/markupview/test/browser_inspector_markup_edit.js +++ b/browser/devtools/markupview/test/browser_inspector_markup_edit.js @@ -234,16 +234,89 @@ function test() { }, after: function() { assertAttributes(doc.querySelector("#node25"), { id: "node25", src: "somefile.html?param1=<a>¶m2=\xfc¶m3='\"'" }); } }, + + { + desc: "Modify inline style containing \"", + before: function() { + assertAttributes(doc.querySelector("#node26"), { + id: "node26", + style: 'background-image: url("moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.org%2F");' + }); + }, + execute: function(after) { + inspector.once("markupmutation", after); + let editor = getContainerForRawNode(markup, doc.querySelector("#node26")).editor; + let attr = editor.attrs["style"].querySelector(".editable"); + + + attr.focus(); + EventUtils.sendKey("return", inspector.panelWin); + + let input = inplaceEditor(attr).input; + let value = input.value; + + is (value, + "style='background-image: url(\"moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.org%2F\");'", + "Value contains actual double quotes" + ); + + value = value.replace(/mozilla\.org/, "mozilla.com"); + input.value = value; + + EventUtils.sendKey("return", inspector.panelWin); + }, + after: function() { + assertAttributes(doc.querySelector("#node26"), { + id: "node26", + style: 'background-image: url("moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.com%2F");' + }); + } + }, + + { + desc: "Modify inline style containing \" and \'", + before: function() { + assertAttributes(doc.querySelector("#node27"), { + id: "node27", + class: 'Double " and single \'' + }); + }, + execute: function(after) { + inspector.once("markupmutation", after); + let editor = getContainerForRawNode(markup, doc.querySelector("#node27")).editor; + let attr = editor.attrs["class"].querySelector(".editable"); + + attr.focus(); + EventUtils.sendKey("return", inspector.panelWin); + + let input = inplaceEditor(attr).input; + let value = input.value; + + is (value, "class=\"Double " and single '\"", "Value contains ""); + + value = value.replace(/Double/, """).replace(/single/, "'"); + input.value = value; + + EventUtils.sendKey("return", inspector.panelWin); + }, + after: function() { + assertAttributes(doc.querySelector("#node27"), { + id: "node27", + class: '" " and \' \'' + }); + } + }, + { desc: "Add an attribute value without closing \"", enteredText: 'style="display: block;', expectedAttributes: { style: "display: block;" } }, {
--- a/browser/devtools/responsivedesign/responsivedesign.jsm +++ b/browser/devtools/responsivedesign/responsivedesign.jsm @@ -682,16 +682,17 @@ ResponsiveUI.prototype = { this.saveCustomSize(); delete this._resizing; if (this.transitionsEnabled) { this.stack.removeAttribute("notransition"); } this.ignoreY = false; + this.ignoreX = false; this.isResizing = false; }, /** * Store the custom size as a pref. */ saveCustomSize: function RUI_saveCustomSize() { Services.prefs.setIntPref("devtools.responsiveUI.customWidth", this.customPreset.width);
--- a/browser/devtools/styleinspector/test/browser_ruleview_inherit.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_inherit.js @@ -37,17 +37,17 @@ function simpleInherit(aInspector, aRule ok(!!inheritRule.inherited, "Rule should consider itself inherited."); is(inheritRule.textProps.length, 1, "Should only display one inherited style"); let inheritProp = inheritRule.textProps[0]; is(inheritProp.name, "color", "color should have been inherited."); styleNode.parentNode.removeChild(styleNode); emptyInherit(); - }).then(null, console.error); + }); } function emptyInherit() { // No inheritable styles, this rule shouldn't show up. let style = '' + '#test2 {' + ' background-color: green;' + @@ -63,17 +63,17 @@ function emptyInherit() is(elementStyle.rules.length, 1, "Should have 1 rule."); let elementRule = elementStyle.rules[0]; ok(!elementRule.inherited, "Element style attribute should not consider itself inherited."); styleNode.parentNode.removeChild(styleNode); elementStyleInherit(); - }).then(null, console.error); + }); } function elementStyleInherit() { doc.body.innerHTML = '<div id="test2" style="color: red"><div id="test1">Styled Node</div></div>'; inspector.selection.setNode(doc.getElementById("test1")); inspector.once("inspector-updated", () => { @@ -87,17 +87,17 @@ function elementStyleInherit() let inheritRule = elementStyle.rules[1]; is(inheritRule.domRule.type, ELEMENT_STYLE, "Inherited rule should be an element style, not a rule."); ok(!!inheritRule.inherited, "Rule should consider itself inherited."); is(inheritRule.textProps.length, 1, "Should only display one inherited style"); let inheritProp = inheritRule.textProps[0]; is(inheritProp.name, "color", "color should have been inherited."); finishTest(); - }).then(null, console.error); + }); } function finishTest() { doc = null; gBrowser.removeCurrentTab(); finish(); }
--- a/browser/devtools/styleinspector/test/browser_styleinspector_bug_672744_search_filter.js +++ b/browser/devtools/styleinspector/test/browser_styleinspector_bug_672744_search_filter.js @@ -32,17 +32,17 @@ function SI_inspectNode() var span = doc.querySelector("#matches"); ok(span, "captain, we have the matches span"); inspector.selection.setNode(span); inspector.once("inspector-updated", () => { is(span, computedView.viewedElement.rawNode(), "style inspector node matches the selected node"); SI_toggleDefaultStyles(); - }).then(null, (err) => console.error(err)); + }); } function SI_toggleDefaultStyles() { info("checking \"Browser styles\" checkbox"); let doc = computedView.styleDocument; let checkbox = doc.querySelector(".includebrowserstyles");
--- a/browser/locales/en-US/chrome/browser/devtools/webConsole.dtd +++ b/browser/locales/en-US/chrome/browser/devtools/webConsole.dtd @@ -55,17 +55,17 @@ <!ENTITY btnPageCSS.label "CSS"> <!ENTITY btnPageCSS.tooltip "Log CSS parsing errors"> <!ENTITY btnPageCSS.accesskey "C"> <!ENTITY btnPageJS.label "JS"> <!ENTITY btnPageJS.tooltip "Log JavaScript exceptions"> <!ENTITY btnPageJS.accesskey "J"> <!ENTITY btnPageSecurity.label "Security"> <!ENTITY btnPageSecurity.tooltip "Log security errors and warnings"> -<!ENTITY btnPageSecurity.accesskey "S"> +<!ENTITY btnPageSecurity.accesskey "u"> <!-- LOCALIZATION NOTE (btnPageLogging): This is used as the text of the - the toolbar. It shows or hides messages that the web developer inserted on - the page for debugging purposes, using calls such console.log() and - console.error(). --> <!ENTITY btnPageLogging.label "Logging"> <!ENTITY btnPageLogging.tooltip "Log messages sent to the window.console object"> <!ENTITY btnPageLogging.accesskey2 "R">
--- a/browser/themes/shared/devtools/markup-view.css +++ b/browser/themes/shared/devtools/markup-view.css @@ -4,22 +4,16 @@ * { padding: 0; margin: 0; } .newattr { cursor: pointer; -} - -/* Give some padding to focusable elements to match the editor input - * that will replace them. */ -span[tabindex] { - display: inline-block; padding: 1px 0; } li.container { padding: 2px 0 0 2px; } .codebox {
--- a/config/rules.mk +++ b/config/rules.mk @@ -25,16 +25,17 @@ endif HOST_CSRCS \ HOST_LIBRARY_NAME \ MODULE \ NO_DIST_INSTALL \ PARALLEL_DIRS \ TEST_DIRS \ TIERS \ TOOL_DIRS \ + XPCSHELL_TESTS \ XPIDL_MODULE \ $(NULL) _DEPRECATED_VARIABLES := \ XPIDL_FLAGS \ $(NULL) ifndef EXTERNALLY_MANAGED_MAKE_FILE
--- a/dom/locales/en-US/chrome/layout/HtmlForm.properties +++ b/dom/locales/en-US/chrome/layout/HtmlForm.properties @@ -23,13 +23,16 @@ NoFileSelected=No file selected. # LOCALIZATION NOTE (NoFilesSelected): this string is shown on a # <input type='file' multiple> when there is no file selected yet. NoFilesSelected=No files selected. # LOCALIZATION NOTE (XFilesSelected): this string is shown on a # <input type='file' multiple> when there are more than one selected file. # %S will be a number greater or equal to 2. XFilesSelected=%S files selected. ColorPicker=Choose a color -# LOCALIZATION NOTE (AndXMoreFiles): this string is shown at the end of the -# tooltip text for <input type='file' multiple> when there are more than 21 -# files selected (when we will only list the first 20, plus an "and X more" -# line). %S will be the number of files minus 20. -AndXMoreFiles=and %S more +# LOCALIZATION NOTE (AndNMoreFiles): Semi-colon list of plural forms. +# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals +# This string is shown at the end of the tooltip text for <input type='file' +# multiple> when there are more than 21 files selected (when we will only list +# the first 20, plus an "and X more" line). #1 represents the number of files +# minus 20 and will always be a number equal to or greater than 2. So the +# singular case will never be used. +AndNMoreFiles=and one more;and #1 more
--- a/js/src/config/rules.mk +++ b/js/src/config/rules.mk @@ -25,16 +25,17 @@ endif HOST_CSRCS \ HOST_LIBRARY_NAME \ MODULE \ NO_DIST_INSTALL \ PARALLEL_DIRS \ TEST_DIRS \ TIERS \ TOOL_DIRS \ + XPCSHELL_TESTS \ XPIDL_MODULE \ $(NULL) _DEPRECATED_VARIABLES := \ XPIDL_FLAGS \ $(NULL) ifndef EXTERNALLY_MANAGED_MAKE_FILE
--- a/mobile/android/base/PageActionLayout.java +++ b/mobile/android/base/PageActionLayout.java @@ -26,39 +26,39 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import java.util.UUID; -import java.util.LinkedHashMap; +import java.util.ArrayList; public class PageActionLayout extends LinearLayout implements GeckoEventListener, View.OnClickListener, View.OnLongClickListener { private final String LOGTAG = "GeckoPageActionLayout"; private final String MENU_BUTTON_KEY = "MENU_BUTTON_KEY"; private final int DEFAULT_PAGE_ACTIONS_SHOWN = 2; - private LinkedHashMap<String, PageAction> mPageActionList; + private ArrayList<PageAction> mPageActionList; private GeckoPopupMenu mPageActionsMenu; private Context mContext; private LinearLayout mLayout; // By default it's two, can be changed by calling setNumberShown(int) private int mMaxVisiblePageActions; public PageActionLayout(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; mLayout = this; - mPageActionList = new LinkedHashMap<String, PageAction>(); + mPageActionList = new ArrayList<PageAction>(); setNumberShown(DEFAULT_PAGE_ACTIONS_SHOWN); refreshPageActionIcons(); registerEventListener("PageActions:Add"); registerEventListener("PageActions:Remove"); } public void setNumberShown(int count) { @@ -86,57 +86,68 @@ public class PageActionLayout extends Li @Override public void handleMessage(String event, JSONObject message) { try { if (event.equals("PageActions:Add")) { final String id = message.getString("id"); final String title = message.getString("title"); final String imageURL = message.optString("icon"); + final boolean mImportant = message.getBoolean("important"); addPageAction(id, title, imageURL, new OnPageActionClickListeners() { @Override public void onClick(String id) { GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PageActions:Clicked", id)); } @Override public boolean onLongClick(String id) { GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PageActions:LongClicked", id)); return true; } - }); + }, mImportant); } else if (event.equals("PageActions:Remove")) { final String id = message.getString("id"); removePageAction(id); } } catch(JSONException ex) { Log.e(LOGTAG, "Error deocding", ex); } } - public void addPageAction(final String id, final String title, final String imageData, final OnPageActionClickListeners mOnPageActionClickListeners) { - final PageAction pageAction = new PageAction(id, title, null, mOnPageActionClickListeners); - mPageActionList.put(id, pageAction); + public void addPageAction(final String id, final String title, final String imageData, final OnPageActionClickListeners mOnPageActionClickListeners, boolean mImportant) { + final PageAction pageAction = new PageAction(id, title, null, mOnPageActionClickListeners, mImportant); + + int insertAt = mPageActionList.size(); + while(insertAt > 0 && mPageActionList.get(insertAt-1).isImportant()) { + insertAt--; + } + mPageActionList.add(insertAt, pageAction); BitmapUtils.getDrawable(mContext, imageData, new BitmapUtils.BitmapLoader() { @Override public void onBitmapFound(final Drawable d) { - if (mPageActionList.containsKey(id)) { + if (mPageActionList.contains(pageAction)) { pageAction.setDrawable(d); refreshPageActionIcons(); } } }); } public void removePageAction(String id) { - mPageActionList.remove(id); - refreshPageActionIcons(); + for(int i = 0; i < mPageActionList.size(); i++) { + if (mPageActionList.get(i).getID().equals(id)) { + mPageActionList.remove(i); + refreshPageActionIcons(); + return; + } + } } private ImageButton createImageButton() { ImageButton imageButton = new ImageButton(mContext, null, R.style.AddressBar_ImageButton_Icon); imageButton.setLayoutParams(new LayoutParams(mContext.getResources().getDimensionPixelSize(R.dimen.page_action_button_width), LayoutParams.MATCH_PARENT)); imageButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE); imageButton.setOnClickListener(this); imageButton.setOnLongClickListener(this); @@ -145,29 +156,29 @@ public class PageActionLayout extends Li @Override public void onClick(View v) { String buttonClickedId = (String)v.getTag(); if (buttonClickedId != null) { if (buttonClickedId.equals(MENU_BUTTON_KEY)) { showMenu(v, mPageActionList.size() - mMaxVisiblePageActions + 1); } else { - mPageActionList.get(buttonClickedId).onClick(); + getPageActionWithId(buttonClickedId).onClick(); } } } @Override public boolean onLongClick(View v) { String buttonClickedId = (String)v.getTag(); if (buttonClickedId.equals(MENU_BUTTON_KEY)) { showMenu(v, mPageActionList.size() - mMaxVisiblePageActions + 1); return true; } else { - return mPageActionList.get(buttonClickedId).onLongClick(); + return getPageActionWithId(buttonClickedId).onLongClick(); } } private void setActionForView(final ImageButton view, final PageAction pageAction) { if (pageAction == null) { view.setTag(null); ThreadUtils.postToUiThread(new Runnable() { @Override @@ -225,79 +236,86 @@ public class PageActionLayout extends Li * and hence we maintain the insertion order of the child Views which is essentially the reverse of their index */ int buttonIndex = (this.getChildCount() - 1) - index; int totalVisibleButtons = ((mPageActionList.size() < this.getChildCount()) ? mPageActionList.size() : this.getChildCount()); if (mPageActionList.size() > buttonIndex) { // Return the pageactions starting from the end of the list for the number of visible pageactions. - return getPageActionAt((mPageActionList.size() - totalVisibleButtons) + buttonIndex); + return mPageActionList.get((mPageActionList.size() - totalVisibleButtons) + buttonIndex); } return null; } - private PageAction getPageActionAt(int index) { - int count = 0; - for(PageAction pageAction : mPageActionList.values()) { - if (count == index) { + private PageAction getPageActionWithId(String id) { + for(int i = 0; i < mPageActionList.size(); i++) { + PageAction pageAction = mPageActionList.get(i); + if (pageAction.getID().equals(id)) { return pageAction; } - count++; } return null; } private void showMenu(View mPageActionButton, int toShow) { if (mPageActionsMenu == null) { mPageActionsMenu = new GeckoPopupMenu(mPageActionButton.getContext(), mPageActionButton); mPageActionsMenu.inflate(0); mPageActionsMenu.setOnMenuItemClickListener(new GeckoPopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { - for(PageAction pageAction : mPageActionList.values()) { - if (pageAction.key() == item.getItemId()) { + int id = item.getItemId(); + for(int i = 0; i < mPageActionList.size(); i++) { + PageAction pageAction = mPageActionList.get(i); + if (pageAction.key() == id) { pageAction.onClick(); + return true; } } - return true; + return false; } }); } Menu menu = mPageActionsMenu.getMenu(); menu.clear(); - int count = 0; - for(PageAction pageAction : mPageActionList.values()) { - if (count < toShow) { + for(int i = 0; i < mPageActionList.size(); i++) { + if (i < toShow) { + PageAction pageAction = mPageActionList.get(i); MenuItem item = menu.add(Menu.NONE, pageAction.key(), Menu.NONE, pageAction.getTitle()); item.setIcon(pageAction.getDrawable()); } - count++; } mPageActionsMenu.show(); } public static interface OnPageActionClickListeners { public void onClick(String id); public boolean onLongClick(String id); } private static class PageAction { private OnPageActionClickListeners mOnPageActionClickListeners; private Drawable mDrawable; private String mTitle; private String mId; private int key; + private boolean mImportant; - public PageAction(String id, String title, Drawable image, OnPageActionClickListeners mOnPageActionClickListeners) { + public PageAction(String id, + String title, + Drawable image, + OnPageActionClickListeners mOnPageActionClickListeners, + boolean mImportant) { this.mId = id; this.mTitle = title; this.mDrawable = image; this.mOnPageActionClickListeners = mOnPageActionClickListeners; + this.mImportant = mImportant; this.key = UUID.fromString(mId.subSequence(1, mId.length() - 2).toString()).hashCode(); } public Drawable getDrawable() { return mDrawable; } @@ -312,16 +330,20 @@ public class PageActionLayout extends Li public String getID() { return mId; } public int key() { return key; } + public boolean isImportant() { + return mImportant; + } + public void onClick() { if (mOnPageActionClickListeners != null) { mOnPageActionClickListeners.onClick(mId); } } public boolean onLongClick() { if (mOnPageActionClickListeners != null) {
--- a/mobile/android/chrome/content/SelectionHandler.js +++ b/mobile/android/chrome/content/SelectionHandler.js @@ -69,23 +69,27 @@ var SelectionHandler = { switch (aTopic) { case "Gesture:SingleTap": { if (this._activeType == this.TYPE_SELECTION) { let data = JSON.parse(aData); if (this._pointInSelection(data.x, data.y)) this.copySelection(); else this._closeSelection(); + } else if (this._activeType == this.TYPE_CURSOR) { + // attachCaret() is called in the "Gesture:SingleTap" handler in BrowserEventHandler + // We're guaranteed to call this first, because this observer was added last + this._closeSelection(); } break; } case "Tab:Selected": this._closeSelection(); break; - + case "Window:Resize": { if (this._activeType == this.TYPE_SELECTION) { // Knowing when the page is done drawing is hard, so let's just cancel // the selection when the window changes. We should fix this later. this._closeSelection(); } break; }
--- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -1623,17 +1623,18 @@ var NativeWindow = { pageactions: { _items: { }, add: function(aOptions) { let id = uuidgen.generateUUID().toString(); sendMessageToJava({ type: "PageActions:Add", id: id, title: aOptions.title, - icon: resolveGeckoURI(aOptions.icon) + icon: resolveGeckoURI(aOptions.icon), + important: "important" in aOptions ? aOptions.important : false }); this._items[id] = { clickCallback: aOptions.clickCallback, longClickCallback: aOptions.longClickCallback }; return id; }, remove: function(id) { @@ -7152,24 +7153,26 @@ let Reader = { NativeWindow.pageactions.remove(this.pageAction.id); delete this.pageAction.id; } if (tab.readerActive) { this.pageAction.id = NativeWindow.pageactions.add({ title: Strings.browser.GetStringFromName("readerMode.exit"), icon: "drawable://reader_active", - clickCallback: this.pageAction.readerModeCallback + clickCallback: this.pageAction.readerModeCallback, + important: true }); } else if (tab.readerEnabled) { this.pageAction.id = NativeWindow.pageactions.add({ title: Strings.browser.GetStringFromName("readerMode.enter"), icon: "drawable://reader", clickCallback:this.pageAction.readerModeCallback, - longClickCallback: this.pageAction.readerModeActiveCallback + longClickCallback: this.pageAction.readerModeActiveCallback, + important: true }); } }, observe: function(aMessage, aTopic, aData) { switch(aTopic) { case "Reader:Add": { let args = JSON.parse(aData);
--- a/services/common/tests/unit/test_storage_server.js +++ b/services/common/tests/unit/test_storage_server.js @@ -341,17 +341,17 @@ add_test(function test_bso_if_unmodified server.createContents("123", { test: {bso: {foo: "bar"}} }); server.startSynchronous(); let coll = server.user("123").collection("test"); do_check_neq(coll, null); - let time = coll.timestamp; + let time = coll.bso("bso").modified; _("Ensure we get a 412 for specified times older than server time."); let request = localRequest(server, "/2.0/123/storage/test/bso", "123", "password"); request.setHeader("X-If-Unmodified-Since", time - 5000); request.setHeader("Content-Type", "application/json"); let payload = JSON.stringify({"payload": "foobar"}); let error = doPutRequest(request, payload);
--- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -3361,16 +3361,28 @@ "description": "The time (in milliseconds) that it took a 'prototypeAndProperties' request to go round trip." }, "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOTYPEANDPROPERTIES_MS": { "kind": "exponential", "high": "10000", "n_buckets": "1000", "description": "The time (in milliseconds) that it took a 'prototypeAndProperties' request to go round trip." }, + "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROTOTYPESANDPROPERTIES_MS": { + "kind": "exponential", + "high": "10000", + "n_buckets": "1000", + "description": "The time (in milliseconds) that it took a 'prototypesAndProperties' request to go round trip." + }, + "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOTYPESANDPROPERTIES_MS": { + "kind": "exponential", + "high": "10000", + "n_buckets": "1000", + "description": "The time (in milliseconds) that it took a 'prototypesAndProperties' request to go round trip." + }, "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROPERTY_MS": { "kind": "exponential", "high": "10000", "n_buckets": "1000", "description": "The time (in milliseconds) that it took a 'property' request to go round trip." }, "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROPERTY_MS": { "kind": "exponential",
--- a/toolkit/content/widgets/popup.xml +++ b/toolkit/content/widgets/popup.xml @@ -585,21 +585,21 @@ const TRUNCATED_FILE_COUNT = 20; let count = Math.min(files.length, TRUNCATED_FILE_COUNT); for (let i = 1; i < count; ++i) { titleText += "\n" + files[i].name; } if (files.length == TRUNCATED_FILE_COUNT + 1) { titleText += "\n" + files[TRUNCATED_FILE_COUNT].name; } else if (files.length > TRUNCATED_FILE_COUNT + 1) { - let xmoreStr = bundle.GetStringFromName("AndXMoreFiles"); + let xmoreStr = bundle.GetStringFromName("AndNMoreFiles"); let xmoreNum = files.length - TRUNCATED_FILE_COUNT; let tmp = {}; Components.utils.import("resource://gre/modules/PluralForm.jsm", tmp); - let andXMoreStr = tmp.PluralForm.get(xmoreNum, xmoreStr).replace("%S", xmoreNum); + let andXMoreStr = tmp.PluralForm.get(xmoreNum, xmoreStr).replace("#1", xmoreNum); titleText += "\n" + andXMoreStr; } } } catch(e) {} } while ((titleText == null) && (XLinkTitleText == null) && (SVGTitleText == null) && tipElement) {
--- a/toolkit/devtools/DevToolsUtils.js +++ b/toolkit/devtools/DevToolsUtils.js @@ -1,35 +1,46 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; /* General utilities used throughout devtools. */ -/* Turn the error e into a string, without fail. */ +/** + * Turn the error |aError| into a string, without fail. + */ this.safeErrorString = function safeErrorString(aError) { try { - var s = aError.toString(); - if (typeof s === "string") - return s; + let errorString = aError.toString(); + if (typeof errorString === "string") { + // Attempt to attach a stack to |errorString|. If it throws an error, or + // isn't a string, don't use it. + try { + if (aError.stack) { + let stack = aError.stack.toString(); + if (typeof stack === "string") { + errorString += "\nStack: " + stack; + } + } + } catch (ee) { } + + return errorString; + } } catch (ee) { } return "<failed trying to find error description>"; } /** * Report that |aWho| threw an exception, |aException|. */ this.reportException = function reportException(aWho, aException) { let msg = aWho + " threw an exception: " + safeErrorString(aException); - if (aException.stack) { - msg += "\nCall stack:\n" + aException.stack; - } dump(msg + "\n"); if (Components.utils.reportError) { /* * Note that the xpcshell test harness registers an observer for * console messages, so when we're running tests, this will cause * the test to quit.
deleted file mode 100644 --- a/toolkit/devtools/apps/tests/Makefile.in +++ /dev/null @@ -1,15 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -DEPTH = @DEPTH@ -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ -relativesrcdir = @relativesrcdir@ - -include $(DEPTH)/config/autoconf.mk - -XPCSHELL_TESTS = unit - -include $(topsrcdir)/config/rules.mk
--- a/toolkit/devtools/apps/tests/moz.build +++ b/toolkit/devtools/apps/tests/moz.build @@ -1,7 +1,9 @@ # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. MODULE = 'test_webapps_actor' + +XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
--- a/toolkit/devtools/apps/tests/unit/xpcshell.ini +++ b/toolkit/devtools/apps/tests/unit/xpcshell.ini @@ -1,6 +1,10 @@ [DEFAULT] head = head_apps.js tail = tail_apps.js [test_webappsActor.js] +# Persistent failures. +skip-if = true [test_appInstall.js] +# Persistent failures. +skip-if = true
--- a/toolkit/devtools/client/dbg-client.jsm +++ b/toolkit/devtools/client/dbg-client.jsm @@ -1621,18 +1621,32 @@ ThreadClient.prototype = { */ source: function TC_source(aForm) { if (aForm.actor in this._threadGrips) { return this._threadGrips[aForm.actor]; } return this._threadGrips[aForm.actor] = new SourceClient(this._client, aForm); - } + }, + /** + * Request the prototype and own properties of mutlipleObjects. + * + * @param aOnResponse function + * Called with the request's response. + * @param actors [string] + * List of actor ID of the queried objects. + */ + getPrototypesAndProperties: DebuggerClient.requester({ + type: "prototypesAndProperties", + actors: args(0) + }, { + telemetry: "PROTOTYPESANDPROPERTIES" + }) }; eventSource(ThreadClient.prototype); /** * Creates a tracing profiler client for the remote debugging protocol * server. This client is a front to the trace actor created on the * server side, hiding the protocol details in a traditional
--- a/toolkit/devtools/moz.build +++ b/toolkit/devtools/moz.build @@ -1,14 +1,16 @@ # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +TEST_DIRS += ['tests'] + PARALLEL_DIRS += [ 'server', 'client', 'gcli', 'sourcemap', 'webconsole', 'apps', 'styleinspector'
--- a/toolkit/devtools/server/actors/script.js +++ b/toolkit/devtools/server/actors/script.js @@ -2065,31 +2065,60 @@ ThreadActor.prototype = { && bp.line <= endLine) { this._setBreakpoint(bp); } } return true; }, + + /** + * Get prototypes and properties of multiple objects. + */ + onPrototypesAndProperties: function TA_onPrototypesAndProperties(aRequest) { + let result = {}; + for (let actorID of aRequest.actors) { + // This code assumes that there are no lazily loaded actors returned + // by this call. + let actor = this.conn.getActor(actorID); + if (!actor) { + return { from: this.actorID, + error: "noSuchActor" }; + } + let handler = actor.onPrototypeAndProperties; + if (!handler) { + return { from: this.actorID, + error: "unrecognizedPacketType", + message: ('Actor "' + actorID + + '" does not recognize the packet type ' + + '"prototypeAndProperties"') }; + } + result[actorID] = handler.call(actor, {}); + } + return { from: this.actorID, + actors: result }; + } + }; ThreadActor.prototype.requestTypes = { "attach": ThreadActor.prototype.onAttach, "detach": ThreadActor.prototype.onDetach, "reconfigure": ThreadActor.prototype.onReconfigure, "resume": ThreadActor.prototype.onResume, "clientEvaluate": ThreadActor.prototype.onClientEvaluate, "frames": ThreadActor.prototype.onFrames, "interrupt": ThreadActor.prototype.onInterrupt, "eventListeners": ThreadActor.prototype.onEventListeners, "releaseMany": ThreadActor.prototype.onReleaseMany, "setBreakpoint": ThreadActor.prototype.onSetBreakpoint, "sources": ThreadActor.prototype.onSources, - "threadGrips": ThreadActor.prototype.onThreadGrips + "threadGrips": ThreadActor.prototype.onThreadGrips, + "prototypesAndProperties": ThreadActor.prototype.onPrototypesAndProperties }; /** * Creates a PauseActor. * * PauseActors exist for the lifetime of a given debuggee pause. Used to * scope pause-lifetime grips.
--- a/toolkit/devtools/server/actors/tracer.js +++ b/toolkit/devtools/server/actors/tracer.js @@ -530,17 +530,17 @@ function timeSinceTraceStarted({ startTi /** * Creates a value grip for the given completion value, to be * serialized by JSON.stringify. * * @param aType string * The type of completion value to serialize (return, throw, or yield). */ function serializeCompletionValue(aType, { value }) { - if (typeof value[aType] === "undefined") { + if (!Object.hasOwnProperty.call(value, aType)) { return undefined; } return createValueGrip(value[aType], true); } // Serialization helper functions. Largely copied from script.js and modified // for use in serialization rather than object actor requests.
--- a/toolkit/devtools/server/main.js +++ b/toolkit/devtools/server/main.js @@ -829,23 +829,22 @@ DebuggerServerConnection.prototype = { if (pool.has(aActorID)) { return pool; } } return null; }, _unknownError: function DSC__unknownError(aPrefix, aError) { - let errorString = safeErrorString(aError); - errorString += "\n" + aError.stack; + let errorString = aPrefix + ": " + safeErrorString(aError); Cu.reportError(errorString); dumpn(errorString); return { error: "unknownError", - message: (aPrefix + "': " + errorString) + message: errorString }; }, /* Forwarding packets to other transports based on actor name prefixes. */ /* * Arrange to forward packets to another server. This is how we * forward debugging connections to child processes.
new file mode 100644 --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_objectgrips-09.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/** + * This tests exercises getProtypesAndProperties message accepted + * by a thread actor. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1, arg2) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect(function() { + attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); + do_test_pending(); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) { + let args = aPacket.frame.arguments; + + gThreadClient.getPrototypesAndProperties([args[0].actor, args[1].actor], function(aResponse) { + let obj1 = aResponse.actors[args[0].actor]; + let obj2 = aResponse.actors[args[1].actor]; + do_check_eq(obj1.ownProperties.x.configurable, true); + do_check_eq(obj1.ownProperties.x.enumerable, true); + do_check_eq(obj1.ownProperties.x.writable, true); + do_check_eq(obj1.ownProperties.x.value, 10); + + do_check_eq(obj1.ownProperties.y.configurable, true); + do_check_eq(obj1.ownProperties.y.enumerable, true); + do_check_eq(obj1.ownProperties.y.writable, true); + do_check_eq(obj1.ownProperties.y.value, "kaiju"); + + do_check_eq(obj2.ownProperties.z.configurable, true); + do_check_eq(obj2.ownProperties.z.enumerable, true); + do_check_eq(obj2.ownProperties.z.writable, true); + do_check_eq(obj2.ownProperties.z.value, 123); + + do_check_true(obj1.prototype != undefined); + do_check_true(obj2.prototype != undefined); + + gThreadClient.resume(function() { + finishClient(gClient); + }); + }); + + }); + + gDebuggee.eval("stopMe({ x: 10, y: 'kaiju'}, { z: 123 })"); +} +
--- a/toolkit/devtools/server/tests/unit/test_trace_actor-06.js +++ b/toolkit/devtools/server/tests/unit/test_trace_actor-06.js @@ -1,14 +1,14 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** - * Tests that objects are correctly serialized and sent in exitedFrame - * packets. + * Tests that values are correctly serialized and sent in enteredFrame + * and exitedFrame packets. */ let {defer} = devtools.require("sdk/core/promise"); var gDebuggee; var gClient; var gTraceClient; @@ -25,122 +25,199 @@ function run_test() }); }); }); do_test_pending(); } function test_enter_exit_frame() { - gTraceClient.addListener("exitedFrame", function(aEvent, aPacket) { - if (aPacket.sequence === 3) { - let obj = aPacket.return; - do_check_eq(typeof obj, "object", - 'exitedFrame response should have return value'); - do_check_eq(typeof obj.prototype, "object", - 'return value should have prototype'); - do_check_eq(typeof obj.ownProperties, "object", - 'return value should have ownProperties list'); - do_check_eq(typeof obj.safeGetterValues, "object", - 'return value should have safeGetterValues'); - - do_check_eq(typeof obj.ownProperties.num, "object", - 'return value should have property "num"'); - do_check_eq(typeof obj.ownProperties.str, "object", - 'return value should have property "str"'); - do_check_eq(typeof obj.ownProperties.bool, "object", - 'return value should have property "bool"'); - do_check_eq(typeof obj.ownProperties.undef, "object", - 'return value should have property "undef"'); - do_check_eq(typeof obj.ownProperties.undef.value, "object", - 'return value property "undef" should be a grip'); - do_check_eq(typeof obj.ownProperties.nil, "object", - 'return value should have property "nil"'); - do_check_eq(typeof obj.ownProperties.nil.value, "object", - 'return value property "nil" should be a grip'); - do_check_eq(typeof obj.ownProperties.obj, "object", - 'return value should have property "obj"'); - do_check_eq(typeof obj.ownProperties.obj.value, "object", - 'return value property "obj" should be a grip'); - do_check_eq(typeof obj.ownProperties.arr, "object", - 'return value should have property "arr"'); - do_check_eq(typeof obj.ownProperties.arr.value, "object", - 'return value property "arr" should be a grip'); - do_check_eq(typeof obj.ownProperties.inf, "object", - 'return value should have property "inf"'); - do_check_eq(typeof obj.ownProperties.inf.value, "object", - 'return value property "inf" should be a grip'); - do_check_eq(typeof obj.ownProperties.ninf, "object", - 'return value should have property "ninf"'); - do_check_eq(typeof obj.ownProperties.ninf.value, "object", - 'return value property "ninf" should be a grip'); - do_check_eq(typeof obj.ownProperties.nan, "object", - 'return value should have property "nan"'); - do_check_eq(typeof obj.ownProperties.nan.value, "object", - 'return value property "nan" should be a grip'); - do_check_eq(typeof obj.ownProperties.nzero, "object", - 'return value should have property "nzero"'); - do_check_eq(typeof obj.ownProperties.nzero.value, "object", - 'return value property "nzero" should be a grip'); - - do_check_eq(obj.prototype.type, "object"); - do_check_eq(obj.ownProperties.num.value, 25); - do_check_eq(obj.ownProperties.str.value, "foo"); - do_check_eq(obj.ownProperties.bool.value, false); - do_check_eq(obj.ownProperties.undef.value.type, "undefined"); - do_check_eq(obj.ownProperties.nil.value.type, "null"); - do_check_eq(obj.ownProperties.obj.value.type, "object"); - do_check_eq(obj.ownProperties.obj.value.class, "Object"); - do_check_eq(obj.ownProperties.arr.value.type, "object"); - do_check_eq(obj.ownProperties.arr.value.class, "Array"); - do_check_eq(obj.ownProperties.inf.value.type, "Infinity"); - do_check_eq(obj.ownProperties.ninf.value.type, "-Infinity"); - do_check_eq(obj.ownProperties.nan.value.type, "NaN"); - do_check_eq(obj.ownProperties.nzero.value.type, "-0"); - } - }); + gTraceClient.addListener("enteredFrame", check_packet); + gTraceClient.addListener("exitedFrame", check_packet); start_trace() .then(eval_code) .then(stop_trace) .then(function() { finishClient(gClient); }); } function start_trace() { let deferred = defer(); - gTraceClient.startTrace(["return"], null, function() { deferred.resolve(); }); + gTraceClient.startTrace(["arguments", "return"], null, function() { deferred.resolve(); }); return deferred.promise; } function eval_code() { gDebuggee.eval("(" + function() { - function foo() { - let obj = {}; - obj.self = obj; + function identity(x) { + return x; + } + + let circular = {}; + circular.self = circular; - return { - num: 25, - str: "foo", - bool: false, - undef: undefined, - nil: null, - obj: obj, - arr: [1,2,3,4,5], - inf: Infinity, - ninf: -Infinity, - nan: NaN, - nzero: -0 - }; - } - foo(); + let obj = { + num: 0, + str: "foo", + bool: false, + undef: undefined, + nil: null, + inf: Infinity, + ninf: -Infinity, + nan: NaN, + nzero: -0, + obj: circular, + arr: [1,2,3,4,5] + }; + + identity(); + identity(0); + identity(""); + identity(false); + identity(undefined); + identity(null); + identity(Infinity); + identity(-Infinity); + identity(NaN); + identity(-0); + identity(obj); } + ")()"); } function stop_trace() { let deferred = defer(); gTraceClient.stopTrace(null, function() { deferred.resolve(); }); return deferred.promise; } + +function check_packet(aEvent, aPacket) +{ + let value = (aPacket.type === "enteredFrame" && aPacket.arguments) + ? aPacket.arguments[0] + : aPacket.return; + switch(aPacket.sequence) { + case 2: + do_check_eq(typeof aPacket.arguments, "object", + "zero-argument function call should send arguments list"); + do_check_eq(aPacket.arguments.length, 0, + "zero-argument function call should send zero-length arguments list"); + break; + case 3: + check_value(value, "object", "undefined"); + break; + case 4: + case 5: + check_value(value, "number", 0); + break; + case 6: + case 7: + check_value(value, "string", ""); + break; + case 8: + case 9: + check_value(value, "boolean", false); + break; + case 10: + case 11: + check_value(value, "object", "undefined"); + break; + case 12: + case 13: + check_value(value, "object", "null"); + break; + case 14: + case 15: + check_value(value, "object", "Infinity"); + break; + case 16: + case 17: + check_value(value, "object", "-Infinity"); + break; + case 18: + case 19: + check_value(value, "object", "NaN"); + break; + case 20: + case 21: + check_value(value, "object", "-0"); + break; + case 22: + case 23: + check_object(aPacket.type, value); + break; + } +} + +function check_value(aActual, aExpectedType, aExpectedValue) +{ + do_check_eq(typeof aActual, aExpectedType); + do_check_eq(aExpectedType === "object" ? aActual.type : aActual, aExpectedValue); +} + +function check_object(aType, aObj) { + do_check_eq(typeof aObj, "object", + 'serialized object should be present in packet'); + do_check_eq(typeof aObj.prototype, "object", + 'serialized object should have prototype'); + do_check_eq(typeof aObj.ownProperties, "object", + 'serialized object should have ownProperties list'); + do_check_eq(typeof aObj.safeGetterValues, "object", + 'serialized object should have safeGetterValues'); + + do_check_eq(typeof aObj.ownProperties.num, "object", + 'serialized object should have property "num"'); + do_check_eq(typeof aObj.ownProperties.str, "object", + 'serialized object should have property "str"'); + do_check_eq(typeof aObj.ownProperties.bool, "object", + 'serialized object should have property "bool"'); + do_check_eq(typeof aObj.ownProperties.undef, "object", + 'serialized object should have property "undef"'); + do_check_eq(typeof aObj.ownProperties.undef.value, "object", + 'serialized object property "undef" should be a grip'); + do_check_eq(typeof aObj.ownProperties.nil, "object", + 'serialized object should have property "nil"'); + do_check_eq(typeof aObj.ownProperties.nil.value, "object", + 'serialized object property "nil" should be a grip'); + do_check_eq(typeof aObj.ownProperties.obj, "object", + 'serialized object should have property "aObj"'); + do_check_eq(typeof aObj.ownProperties.obj.value, "object", + 'serialized object property "aObj" should be a grip'); + do_check_eq(typeof aObj.ownProperties.arr, "object", + 'serialized object should have property "arr"'); + do_check_eq(typeof aObj.ownProperties.arr.value, "object", + 'serialized object property "arr" should be a grip'); + do_check_eq(typeof aObj.ownProperties.inf, "object", + 'serialized object should have property "inf"'); + do_check_eq(typeof aObj.ownProperties.inf.value, "object", + 'serialized object property "inf" should be a grip'); + do_check_eq(typeof aObj.ownProperties.ninf, "object", + 'serialized object should have property "ninf"'); + do_check_eq(typeof aObj.ownProperties.ninf.value, "object", + 'serialized object property "ninf" should be a grip'); + do_check_eq(typeof aObj.ownProperties.nan, "object", + 'serialized object should have property "nan"'); + do_check_eq(typeof aObj.ownProperties.nan.value, "object", + 'serialized object property "nan" should be a grip'); + do_check_eq(typeof aObj.ownProperties.nzero, "object", + 'serialized object should have property "nzero"'); + do_check_eq(typeof aObj.ownProperties.nzero.value, "object", + 'serialized object property "nzero" should be a grip'); + + do_check_eq(aObj.prototype.type, "object"); + do_check_eq(aObj.ownProperties.num.value, 0); + do_check_eq(aObj.ownProperties.str.value, "foo"); + do_check_eq(aObj.ownProperties.bool.value, false); + do_check_eq(aObj.ownProperties.undef.value.type, "undefined"); + do_check_eq(aObj.ownProperties.nil.value.type, "null"); + do_check_eq(aObj.ownProperties.obj.value.type, "object"); + do_check_eq(aObj.ownProperties.obj.value.class, "Object"); + do_check_eq(aObj.ownProperties.arr.value.type, "object"); + do_check_eq(aObj.ownProperties.arr.value.class, "Array"); + do_check_eq(aObj.ownProperties.inf.value.type, "Infinity"); + do_check_eq(aObj.ownProperties.ninf.value.type, "-Infinity"); + do_check_eq(aObj.ownProperties.nan.value.type, "NaN"); + do_check_eq(aObj.ownProperties.nzero.value.type, "-0"); +}
--- a/toolkit/devtools/server/tests/unit/xpcshell.ini +++ b/toolkit/devtools/server/tests/unit/xpcshell.ini @@ -122,16 +122,17 @@ reason = bug 820380 [test_objectgrips-01.js] [test_objectgrips-02.js] [test_objectgrips-03.js] [test_objectgrips-04.js] [test_objectgrips-05.js] [test_objectgrips-06.js] [test_objectgrips-07.js] [test_objectgrips-08.js] +[test_objectgrips-09.js] [test_interrupt.js] [test_stepping-01.js] [test_stepping-02.js] [test_stepping-03.js] [test_stepping-04.js] [test_stepping-05.js] [test_stepping-06.js] [test_framebindings-01.js]
new file mode 100644 --- /dev/null +++ b/toolkit/devtools/tests/moz.build @@ -0,0 +1,9 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MODULE = 'test_devtools' + +XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
new file mode 100644 --- /dev/null +++ b/toolkit/devtools/tests/unit/head_devtools.js @@ -0,0 +1,42 @@ +"use strict"; +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; +const Cr = Components.results; + +Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm"); + +// Register a console listener, so console messages don't just disappear +// into the ether. +let errorCount = 0; +let listener = { + observe: function (aMessage) { + errorCount++; + try { + // If we've been given an nsIScriptError, then we can print out + // something nicely formatted, for tools like Emacs to pick up. + var scriptError = aMessage.QueryInterface(Ci.nsIScriptError); + dump(aMessage.sourceName + ":" + aMessage.lineNumber + ": " + + scriptErrorFlagsToKind(aMessage.flags) + ": " + + aMessage.errorMessage + "\n"); + var string = aMessage.errorMessage; + } catch (x) { + // Be a little paranoid with message, as the whole goal here is to lose + // no information. + try { + var string = "" + aMessage.message; + } catch (x) { + var string = "<error converting error message to string>"; + } + } + + // Make sure we exit all nested event loops so that the test can finish. + while (DebuggerServer.xpcInspector.eventLoopNestLevel > 0) { + DebuggerServer.xpcInspector.exitNestedEventLoop(); + } + do_throw("head_dbg.js got console message: " + string + "\n"); + } +}; + +let consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService); +consoleService.registerListener(listener);
new file mode 100644 --- /dev/null +++ b/toolkit/devtools/tests/unit/test_safeErrorString.js @@ -0,0 +1,54 @@ +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test DevToolsUtils.safeErrorString + +function run_test() { + test_with_error(); + test_with_tricky_error(); + test_with_string(); + test_with_thrower(); + test_with_psychotic(); +} + +function test_with_error() { + let s = DevToolsUtils.safeErrorString(new Error("foo bar")); + // Got the message. + do_check_true(s.contains("foo bar")); + // Got the stack. + do_check_true(s.contains("test_with_error")) + do_check_true(s.contains("test_safeErrorString.js")); +} + +function test_with_tricky_error() { + let e = new Error("batman"); + e.stack = { toString: Object.create(null) }; + let s = DevToolsUtils.safeErrorString(e); + // Still got the message, despite a bad stack property. + do_check_true(s.contains("batman")); +} + +function test_with_string() { + let s = DevToolsUtils.safeErrorString("not really an error"); + // Still get the message. + do_check_true(s.contains("not really an error")); +} + +function test_with_thrower() { + let s = DevToolsUtils.safeErrorString({ + toString: () => { + throw new Error("Muahahaha"); + } + }); + // Still don't fail, get string back. + do_check_eq(typeof s, "string"); +} + +function test_with_psychotic() { + let s = DevToolsUtils.safeErrorString({ + toString: () => Object.create(null) + }); + // Still get a string out, and no exceptions thrown + do_check_eq(typeof s, "string"); +}
new file mode 100644 --- /dev/null +++ b/toolkit/devtools/tests/unit/xpcshell.ini @@ -0,0 +1,5 @@ +[DEFAULT] +head = head_devtools.js +tail = + +[test_safeErrorString.js] \ No newline at end of file
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -2202,16 +2202,21 @@ NS_IMETHODIMP nsExternalAppHandler::Canc { NS_ENSURE_ARG(NS_FAILED(aReason)); // XXX should not ignore the reason mCanceled = true; if (mSaver) { mSaver->Finish(aReason); mSaver = nullptr; + } else if (mStopRequestIssued && mTempFile) { + // This branch can only happen when the user cancels the helper app dialog + // when the request has completed. The temp file has to be removed here, + // because mSaver has been released at that time with the temp file left. + (void)mTempFile->Remove(false); } // Break our reference cycle with the helper app dialog (set up in // OnStartRequest) mDialog = nullptr; mRequest = nullptr;
--- a/webapprt/ContentPermission.js +++ b/webapprt/ContentPermission.js @@ -1,84 +1,105 @@ /* 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/. */ const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; +const UNKNOWN_FAIL = ["geolocation", "desktop-notification"]; + Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://webapprt/modules/WebappRT.jsm"); function ContentPermission() {} ContentPermission.prototype = { classID: Components.ID("{07ef5b2e-88fb-47bd-8cec-d3b0bef11ac4}"), QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]), + _getChromeWindow: function(aWindow) { + return aWindow + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow) + .QueryInterface(Ci.nsIDOMChromeWindow); + }, + prompt: function(request) { - // Only handle geolocation requests for now - if (request.type != "geolocation") { - return; + // Reuse any remembered permission preferences + let result = + Services.perms.testExactPermissionFromPrincipal(request.principal, + request.type); + + // We used to use the name "geo" for the geolocation permission, now we're + // using "geolocation". We need to check both to support existing + // installations. + if ((result == Ci.nsIPermissionManager.UNKNOWN_ACTION || + result == Ci.nsIPermissionManager.PROMPT_ACTION) && + request.type == "geolocation") { + let geoResult = Services.perms.testExactPermission(request.principal.URI, + "geo"); + // We override the result only if the "geo" permission was allowed or + // denied. + if (geoResult == Ci.nsIPermissionManager.ALLOW_ACTION || + geoResult == Ci.nsIPermissionManager.DENY_ACTION) { + result = geoResult; + } } - // Reuse any remembered permission preferences - let result = Services.perms.testExactPermissionFromPrincipal(request.principal, "geo"); if (result == Ci.nsIPermissionManager.ALLOW_ACTION) { request.allow(); return; - } - else if (result == Ci.nsIPermissionManager.DENY_ACTION) { + } else if (result == Ci.nsIPermissionManager.DENY_ACTION || + (result == Ci.nsIPermissionManager.UNKNOWN_ACTION && + UNKNOWN_FAIL.indexOf(request.type) >= 0)) { request.cancel(); return; } - function getChromeWindow(aWindow) { - var chromeWin = aWindow - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem) - .rootTreeItem - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow) - .QueryInterface(Ci.nsIDOMChromeWindow); - return chromeWin; - } - // Display a prompt at the top level let {name} = WebappRT.config.app.manifest; let requestingWindow = request.window.top; - let chromeWin = getChromeWindow(requestingWindow); + let chromeWin = this._getChromeWindow(requestingWindow); let bundle = Services.strings.createBundle("chrome://webapprt/locale/webapp.properties"); // Construct a prompt with share/don't and remember checkbox let remember = {value: false}; let choice = Services.prompt.confirmEx( chromeWin, - bundle.formatStringFromName("geolocation.title", [name], 1), - bundle.GetStringFromName("geolocation.description"), + bundle.formatStringFromName(request.type + ".title", [name], 1), + bundle.GetStringFromName(request.type + ".description"), // Set both buttons to strings with the cancel button being default Ci.nsIPromptService.BUTTON_POS_1_DEFAULT | Ci.nsIPromptService.BUTTON_TITLE_IS_STRING * Ci.nsIPromptService.BUTTON_POS_0 | Ci.nsIPromptService.BUTTON_TITLE_IS_STRING * Ci.nsIPromptService.BUTTON_POS_1, - bundle.GetStringFromName("geolocation.sharelocation"), - bundle.GetStringFromName("geolocation.dontshare"), + bundle.GetStringFromName(request.type + ".allow"), + bundle.GetStringFromName(request.type + ".deny"), null, - bundle.GetStringFromName("geolocation.remember"), + bundle.GetStringFromName(request.type + ".remember"), remember); - // Persist the choice if the user wants to remember + let action = Ci.nsIPermissionManager.ALLOW_ACTION; + if (choice != 0) { + action = Ci.nsIPermissionManager.DENY_ACTION; + } + if (remember.value) { - let action = Ci.nsIPermissionManager.ALLOW_ACTION; - if (choice != 0) { - action = Ci.nsIPermissionManager.DENY_ACTION; - } - Services.perms.addFromPrincipal(request.principal, "geo", action); + // Persist the choice if the user wants to remember + Services.perms.addFromPrincipal(request.principal, request.type, action); + } else { + // Otherwise allow the permission for the current session + Services.perms.addFromPrincipal(request.principal, request.type, action, + Ci.nsIPermissionManager.EXPIRE_SESSION); } // Trigger the selected choice if (choice == 0) { request.allow(); } else { request.cancel();
--- a/webapprt/content/webapp.xul +++ b/webapprt/content/webapp.xul @@ -40,17 +40,17 @@ key="©Cmd.key;" modifiers="accel"/> <key id="key_paste" key="&pasteCmd.key;" modifiers="accel"/> <key id="key_delete" keycode="VK_DELETE" command="cmd_delete"/> <key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/> <key id="key_quitApplication" - key="&quitApplicationCmdMac.key;" + key="&quitApplicationCmdUnix.key;" command="cmd_quitApplication" modifiers="accel"/> <key id="key_hideThisAppCmdMac" key="&hideThisAppCmdMac.key;" modifiers="accel"/> <key id="key_hideOtherAppsCmdMac" key="&hideOtherAppsCmdMac.key;" modifiers="accel,alt"/>
--- a/webapprt/locales/en-US/webapprt/webapp.dtd +++ b/webapprt/locales/en-US/webapprt/webapp.dtd @@ -13,22 +13,24 @@ <!ENTITY quitApplicationCmdWin.label "Exit"> <!ENTITY quitApplicationCmdWin.accesskey "x"> <!ENTITY quitApplicationCmd.label "Quit"> <!ENTITY quitApplicationCmd.accesskey "Q"> <!-- On Mac, we create the Quit and Hide command labels dynamically, - using properties in window.properties, in order to include the name - of the webapp in the labels without creating a DTD file for it. --> -<!ENTITY quitApplicationCmdMac.key "Q"> <!ENTITY hideThisAppCmdMac.key "H"> <!ENTITY hideOtherAppsCmdMac.label "Hide Others"> <!ENTITY hideOtherAppsCmdMac.key "H"> <!ENTITY showAllAppsCmdMac.label "Show All"> +<!-- LOCALIZATION NOTE(quitApplicationCmdUnix.key): This keyboard shortcut is used by both Linux and OSX --> +<!ENTITY quitApplicationCmdUnix.key "Q"> + <!ENTITY editMenu.label "Edit"> <!ENTITY editMenu.accesskey "E"> <!ENTITY undoCmd.label "Undo"> <!ENTITY undoCmd.key "Z"> <!ENTITY undoCmd.accesskey "U"> <!ENTITY redoCmd.label "Redo"> <!ENTITY redoCmd.key "Y"> <!ENTITY redoCmd.accesskey "R">
--- a/webapprt/locales/en-US/webapprt/webapp.properties +++ b/webapprt/locales/en-US/webapprt/webapp.properties @@ -15,20 +15,28 @@ quitApplicationCmdMac.label=Quit %S # LOCALIZATION NOTE (hideApplicationCmdMac.label): %S will be replaced with # the name of the webapp. hideApplicationCmdMac.label=Hide %S # LOCALIZATION NOTE (geolocation.title): %S will be replaced with the name of # the webapp. geolocation.title=%S - Share Location geolocation.description=Do you want to share your location? -geolocation.sharelocation=Share Location -geolocation.dontshare=Don't Share +geolocation.allow=Share Location +geolocation.deny=Don't Share geolocation.remember=Remember my choice +# LOCALIZATION NOTE (desktop-notification.title): %S will be replaced with the +# name of the webapp. +desktop-notification.title=%S - Show notifications +desktop-notification.description=Do you want to allow notifications? +desktop-notification.allow=Show +desktop-notification.deny=Don't show +desktop-notification.remember=Remember my choice + # LOCALIZATION NOTE (webapps.install.title): %S will be replaced with the name # of the webapp being installed. webapps.install.title=Install %S # LOCALIZATION NOTE (webapps.install.description): %S will be replaced with the # name of the webapp being installed. webapps.install.description=Do you want to install %S? webapps.install.install=Install App webapps.install.dontinstall=Don't Install