merge mozilla-central to autoland. r=merge a=merge

This commit is contained in:
Sebastian Hengst 2017-04-22 10:49:35 +02:00
commit 7fccfb74e9
45 changed files with 784 additions and 160 deletions

View File

@ -8,8 +8,25 @@ module.exports = {
"rules": {
// XXX Bug 1326071 - This should be reduced down - probably to 20 or to
// be removed & synced with the mozilla/recommended value.
"complexity": ["error", {"max": 42}],
"complexity": ["error", {"max": 40}],
// Disallow empty statements. This will report an error for:
// try { something(); } catch (e) {}
// but will not report it for:
// try { something(); } catch (e) { /* Silencing the error because ...*/ }
// which is a valid use case.
"no-empty": "error",
// No spaces between function name and parentheses
"no-spaced-func": "error",
// Maximum depth callbacks can be nested.
"max-nested-callbacks": ["error", 8],
// Disallow adding to native types
"no-extend-native": "error",
"no-mixed-spaces-and-tabs": "error",
"no-shadow": "error",
}
};

View File

@ -112,7 +112,7 @@ this.tabs = class extends ExtensionAPI {
onCreated: new SingletonEventManager(context, "tabs.onCreated", fire => {
let listener = (eventName, event) => {
fire.async(tabManager.convert(event.nativeTab));
fire.async(tabManager.convert(event.nativeTab, event.currentTab));
};
tabTracker.on("tab-created", listener);
@ -360,6 +360,7 @@ this.tabs = class extends ExtensionAPI {
options.disallowInheritPrincipal = true;
tabListener.initTabReady();
let currentTab = window.gBrowser.selectedTab;
let nativeTab = window.gBrowser.addTab(url || window.BROWSER_NEW_TAB_URL, options);
let active = true;
@ -394,7 +395,7 @@ this.tabs = class extends ExtensionAPI {
tabListener.initializingTabs.add(nativeTab);
}
return tabManager.convert(nativeTab);
return tabManager.convert(nativeTab, currentTab);
});
},

View File

@ -238,13 +238,18 @@ class TabTracker extends TabTrackerBase {
});
}
// Save the current tab, since the newly-created tab will likely be
// active by the time the promise below resolves and the event is
// dispatched.
let currentTab = nativeTab.ownerGlobal.gBrowser.selectedTab;
// We need to delay sending this event until the next tick, since the
// tab does not have its final index when the TabOpen event is dispatched.
Promise.resolve().then(() => {
if (event.detail.adoptedTab) {
this.emitAttached(event.originalTarget);
} else {
this.emitCreated(event.originalTarget);
this.emitCreated(event.originalTarget, currentTab);
}
});
break;
@ -389,10 +394,12 @@ class TabTracker extends TabTrackerBase {
*
* @param {NativeTab} nativeTab
* The tab element which is being created.
* @param {NativeTab} [currentTab]
* The tab element for the currently active tab.
* @private
*/
emitCreated(nativeTab) {
this.emit("tab-created", {nativeTab});
emitCreated(nativeTab, currentTab) {
this.emit("tab-created", {nativeTab, currentTab});
}
/**
@ -479,12 +486,18 @@ class Tab extends TabBase {
return this.nativeTab.linkedBrowser;
}
get frameLoader() {
// If we don't have a frameLoader yet, just return a dummy with no width and
// height.
return super.frameLoader || {lazyWidth: 0, lazyHeight: 0};
}
get cookieStoreId() {
return getCookieStoreIdForTab(this, this.nativeTab);
}
get height() {
return this.browser.clientHeight;
return this.frameLoader.lazyHeight;
}
get index() {
@ -525,7 +538,7 @@ class Tab extends TabBase {
}
get width() {
return this.browser.clientWidth;
return this.frameLoader.lazyWidth;
}
get window() {

View File

@ -19,7 +19,6 @@ module.exports = {
"no-eval": "error",
"no-extend-native": "error",
"no-fallthrough": ["error", { "commentPattern": ".*[Ii]ntentional(?:ly)?\\s+fall(?:ing)?[\\s-]*through.*" }],
"no-mixed-spaces-and-tabs": "error",
"no-multi-str": "error",
"no-return-assign": "error",
"no-sequences": "error",

View File

@ -511,6 +511,8 @@ PlacesViewBase.prototype = {
if (elt.localName == "menupopup") {
elt = elt.parentNode;
}
// We must remove and reset the attribute to force an update.
elt.removeAttribute("image");
elt.setAttribute("image", aPlacesNode.icon);
},

View File

@ -781,8 +781,11 @@ PlacesTreeView.prototype = {
return;
let column = this._findColumnByType(aColumnType);
if (column && !column.element.hidden)
if (column && !column.element.hidden) {
if (aColumnType == this.COLUMN_TYPE_TITLE)
this._tree.removeImageCacheEntry(row, column);
this._tree.invalidateCell(row, column);
}
// Last modified time is altered for almost all node changes.
if (aColumnType != this.COLUMN_TYPE_LASTMODIFIED) {

View File

@ -53,6 +53,9 @@ support-files =
skip-if = true # temporarily disabled for breaking the treeview - bug 658744
[browser_sort_in_library.js]
[browser_toolbarbutton_menu_context.js]
[browser_views_iconsupdate.js]
support-files =
favicon-normal16.png
[browser_views_liveupdate.js]
[browser_bookmark_all_tabs.js]
support-files =

View File

@ -0,0 +1,119 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests Places views (toolbar, tree) for icons update.
* The menu is not tested since it uses the same code as the toolbar.
*/
add_task(function* () {
const PAGE_URI = NetUtil.newURI("http://places.test/");
const ICON_URI = NetUtil.newURI("http://mochi.test:8888/browser/browser/components/places/tests/browser/favicon-normal16.png");
info("Uncollapse the personal toolbar if needed");
let toolbar = document.getElementById("PersonalToolbar");
let wasCollapsed = toolbar.collapsed;
if (wasCollapsed) {
yield promiseSetToolbarVisibility(toolbar, true);
registerCleanupFunction(function* () {
yield promiseSetToolbarVisibility(toolbar, false);
});
}
info("Open the bookmarks sidebar");
let sidebar = document.getElementById("sidebar");
let promiseSidebarLoaded = new Promise(resolve => {
sidebar.addEventListener("load", resolve, {capture: true, once: true});
});
SidebarUI.show("viewBookmarksSidebar");
registerCleanupFunction(() => {
SidebarUI.hide();
});
yield promiseSidebarLoaded;
// Add a bookmark to the bookmarks toolbar.
let bm = yield PlacesUtils.bookmarks.insert({
url: PAGE_URI,
title: "test icon",
parentGuid: PlacesUtils.bookmarks.toolbarGuid
});
registerCleanupFunction(function* () {
yield PlacesUtils.bookmarks.remove(bm);
});
// The icon is read asynchronously from the network, we don't have an easy way
// to wait for that.
yield new Promise(resolve => {
setTimeout(resolve, 3000);
});
let toolbarElt = getNodeForToolbarItem(bm.guid);
let toolbarShot1 = TestUtils.screenshotArea(toolbarElt, window);
let sidebarRect = yield getRectForSidebarItem(bm.guid);
let sidebarShot1 = TestUtils.screenshotArea(sidebarRect, window);
yield new Promise(resolve => {
PlacesUtils.favicons.setAndFetchFaviconForPage(
PAGE_URI, ICON_URI, true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
resolve,
Services.scriptSecurityManager.getSystemPrincipal()
);
});
// The icon is read asynchronously from the network, we don't have an easy way
// to wait for that.
yield new Promise(resolve => {
setTimeout(resolve, 3000);
});
// Assert.notEqual truncates the strings, so it is unusable here for failure
// debugging purposes.
let toolbarShot2 = TestUtils.screenshotArea(toolbarElt, window);
if (toolbarShot1 != toolbarShot2) {
info("Before toolbar: " + toolbarShot1);
info("After toolbar: " + toolbarShot2);
}
Assert.notEqual(toolbarShot1, toolbarShot2, "The UI should have updated");
let sidebarShot2 = TestUtils.screenshotArea(sidebarRect, window);
if (sidebarShot1 != sidebarShot2) {
info("Before sidebar: " + sidebarShot1);
info("After sidebar: " + sidebarShot2);
}
Assert.notEqual(sidebarShot1, sidebarShot2, "The UI should have updated");
});
/**
* Get Element for a bookmark in the bookmarks toolbar.
*
* @param guid
* GUID of the item to search.
* @returns DOM Node of the element.
*/
function getNodeForToolbarItem(guid) {
return Array.from(document.getElementById("PlacesToolbarItems").childNodes)
.find(child => child._placesNode && child._placesNode.bookmarkGuid == guid);
}
/**
* Get a rect for a bookmark in the bookmarks sidebar
*
* @param guid
* GUID of the item to search.
* @returns DOM Node of the element.
*/
function* getRectForSidebarItem(guid) {
let itemId = yield PlacesUtils.promiseItemId(guid);
let sidebar = document.getElementById("sidebar");
let tree = sidebar.contentDocument.getElementById("bookmarks-view");
tree.selectItems([itemId]);
let rect = {};
[rect.left, rect.top, rect.width, rect.height] = tree.treeBoxObject
.selectionRegion
.getRects();
// Adjust the position for the sidebar.
rect.left += sidebar.getBoundingClientRect().left;
rect.top += sidebar.getBoundingClientRect().top;
return rect;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

View File

@ -4,6 +4,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
"resource://testing-common/PlacesTestUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TestUtils",
"resource://testing-common/TestUtils.jsm");
// Imported via PlacesOverlay.xul
/* global doGetPlacesControllerForCommand:false, PlacesControllerDragHelper:false,

View File

@ -1076,22 +1076,23 @@
<handlers>
<handler event="popupshowing"><![CDATA[
if (!this._computedMinWidth) {
// The panel width only spans to the textbox size, but we also want it
// to include the magnifier icon's width.
let ltr = getComputedStyle(this).direction == "ltr";
let magnifierWidth = parseInt(getComputedStyle(this)[
ltr ? "marginLeft" : "marginRight"
]) * -1;
// Ensure the panel is wide enough to fit at least 3 engines.
let minWidth = Math.max(
parseInt(this.width) + magnifierWidth,
this.oneOffButtons.buttonWidth * 3
);
this.style.minWidth = minWidth + "px";
// Force the panel to have the width of the searchbar rather than
// the width of the textfield.
let DOMUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let textboxRect = DOMUtils.getBoundsWithoutFlushing(this.mInput);
let inputRect = DOMUtils.getBoundsWithoutFlushing(this.mInput.inputField);
this._computedMinWidth = true;
}
// Ensure the panel is wide enough to fit at least 3 engines.
let minWidth = Math.max(textboxRect.width,
this.oneOffButtons.buttonWidth * 3);
this.style.minWidth = minWidth + "px";
// Alignment of the panel with the searchbar is obtained with negative
// margins.
this.style.marginLeft = (textboxRect.left - inputRect.left) + "px";
// This second margin is needed when the direction is reversed,
// eg. when using command+shift+X.
this.style.marginRight = (inputRect.right - textboxRect.right) + "px";
// First handle deciding if we are showing the reduced version of the
// popup containing only the preferences button. We do this if the

View File

@ -12,7 +12,7 @@ module.exports = {
"rules": {
// Rules from the mozilla plugin
"mozilla/balanced-listeners": "error",
"mozilla/no-aArgs": "warn",
"mozilla/no-aArgs": "error",
"mozilla/no-cpows-in-tests": "error",
"mozilla/var-only-at-top-level": "error",
@ -58,16 +58,6 @@ module.exports = {
// Use [] instead of Array()
"no-array-constructor": "error",
// Disallow empty statements. This will report an error for:
// try { something(); } catch (e) {}
// but will not report it for:
// try { something(); } catch (e) { /* Silencing the error because ...*/ }
// which is a valid use case.
"no-empty": "error",
// No spaces between function name and parentheses
"no-spaced-func": "warn",
// No expressions where a statement is expected
"no-unused-expressions": "error",
@ -117,9 +107,6 @@ module.exports = {
// Disallow use of eval(). We have other APIs to evaluate code in content.
"no-eval": "error",
// Disallow adding to native types
"no-extend-native": "error",
// Disallow fallthrough of case statements, except if there is a comment.
"no-fallthrough": "error",
@ -164,9 +151,6 @@ module.exports = {
// Disallow function or variable declarations in nested blocks
"no-inner-declarations": "error",
// Disallow labels that share a name with a variable
"no-label-var": "error",
// Disallow creating new instances of String, Number, and Boolean
"no-new-wrappers": "error",
},

View File

@ -49,12 +49,12 @@ var FormAutofillFrameScript = {
}
},
receiveMessage(aMessage) {
receiveMessage(message) {
if (!Services.prefs.getBoolPref("browser.formautofill.enabled")) {
return;
}
switch (aMessage.name) {
switch (message.name) {
case "FormAutofill:PreviewProfile":
case "FormAutoComplete:PopupClosed":
FormAutofillContent._previewProfile(content.document);

View File

@ -2,17 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#PopupSearchAutoComplete {
/* JS code forces the panel to have the width of the searchbar rather than
* the width of the textfield. Alignment of the panel with the searchbar is
* obtained with negative margins here: margin-inline-start when the text
* field is in the same direction as the rest of the UI, margin-inline-end
* when the textfield's direction has been reversed.
* (eg. using ctrl+shift+X) */
margin-inline-start: -23px;
margin-inline-end: -16px;
}
.autocomplete-textbox-container {
-moz-box-align: stretch;
}

View File

@ -2,17 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#PopupSearchAutoComplete {
/* JS code forces the panel to have the width of the searchbar rather than
* the width of the textfield. Alignment of the panel with the searchbar is
* obtained with negative margins here: margin-inline-start when the text
* field is in the same direction as the rest of the UI, margin-inline-end
* when the textfield's direction has been reversed.
* (eg. using command+shift+X) */
margin-inline-start: -23px;
margin-inline-end: -21px;
}
.searchbar-textbox {
border-radius: 10000px;
}

View File

@ -2,17 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#PopupSearchAutoComplete {
/* JS code forces the panel to have the width of the searchbar rather than
* the width of the textfield. Alignment of the panel with the searchbar is
* obtained with negative margins here: margin-inline-start when the text
* field is in the same direction as the rest of the UI, margin-inline-end
* when the textfield's direction has been reversed.
* (eg. using ctrl+shift+X) */
margin-inline-start: -25px;
margin-inline-end: -18px;
}
.autocomplete-textbox-container {
-moz-box-align: stretch;
}

View File

@ -914,7 +914,11 @@ cargo_build_flags += --frozen
cargo_build_flags += --manifest-path $(CARGO_FILE)
ifdef BUILD_VERBOSE_LOG
cargo_build_flags += --verbose
endif
else
ifdef MOZ_AUTOMATION
cargo_build_flags += --verbose
endif # MOZ_AUTOMATION
endif # BUILD_VERBOSE_LOG
# Enable color output if original stdout was a TTY and color settings
# aren't already present. This essentially restores the default behavior

View File

@ -204,4 +204,10 @@ interface TreeBoxObject : BoxObject {
* Called on a theme switch to flush out the tree's style and image caches.
*/
void clearStyleAndImageCaches();
/**
* Remove an image source from the image cache to allow its invalidation.
*/
[Throws]
void removeImageCacheEntry(long row, TreeColumn col);
};

View File

@ -10,6 +10,8 @@ using CSSToScreenScale from "Units.h";
using ScreenIntSize from "Units.h";
using ScreenPoint from "Units.h";
include "mozilla/GfxMessageUtils.h";
namespace mozilla {
namespace layers {

View File

@ -296,6 +296,17 @@ nsPluginFrame::PrepForDrawing(nsIWidget *aWidget)
return NS_ERROR_FAILURE;
}
// We can already have mInnerView if our instance owner went away and then
// came back. So clear the old one before creating a new one.
if (mInnerView) {
if (mInnerView->GetWidget()) {
// The widget listener should have already been cleared by
// SetInstanceOwner (with a null instance owner).
MOZ_RELEASE_ASSERT(mInnerView->GetWidget()->GetWidgetListener() == nullptr);
}
mInnerView->Destroy();
mInnerView = nullptr;
}
mInnerView = viewMan->CreateView(GetContentRectRelativeToSelf(), view);
if (!mInnerView) {
NS_ERROR("Could not create inner view");

View File

@ -667,6 +667,24 @@ TreeBoxObject::ClearStyleAndImageCaches()
return NS_OK;
}
NS_IMETHODIMP
TreeBoxObject::RemoveImageCacheEntry(int32_t aRowIndex, nsITreeColumn* aCol)
{
NS_ENSURE_ARG(aCol);
NS_ENSURE_TRUE(aRowIndex >= 0, NS_ERROR_INVALID_ARG);
nsTreeBodyFrame* body = GetTreeBodyFrame();
if (body) {
return body->RemoveImageCacheEntry(aRowIndex, aCol);
}
return NS_OK;
}
void
TreeBoxObject::RemoveImageCacheEntry(int32_t row, nsITreeColumn& col, ErrorResult& aRv)
{
aRv = RemoveImageCacheEntry(row, &col);
}
void
TreeBoxObject::ClearCachedValues()
{

View File

@ -75,6 +75,8 @@ public:
bool IsCellCropped(int32_t row, nsITreeColumn* col, ErrorResult& aRv);
void RemoveImageCacheEntry(int32_t row, nsITreeColumn& col, ErrorResult& aRv);
// Deprecated APIs from old IDL
void GetCellAt(JSContext* cx,
int32_t x, int32_t y,

View File

@ -48,7 +48,7 @@ interface nsITreeBoxObject : nsISupports
readonly attribute long rowWidth;
/**
* Get the pixel position of the horizontal scrollbar.
* Get the pixel position of the horizontal scrollbar.
*/
readonly attribute long horizontalPosition;
@ -186,4 +186,12 @@ interface nsITreeBoxObject : nsISupports
* Called on a theme switch to flush out the tree's style and image caches.
*/
void clearStyleAndImageCaches();
/**
* Remove an image source from the image cache to allow its invalidation.
*
* @note This only affects images supplied by the view, not the ones supplied
* through the styling context, like twisties or checkboxes.
*/
void removeImageCacheEntry(in long row, in nsITreeColumn col);
};

View File

@ -1109,7 +1109,7 @@ nsTreeBodyFrame::GetCoordsForCellItem(int32_t aRow, nsITreeColumn* aCol, const n
bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
nscoord currX = mInnerBox.x - mHorzPosition;
// The Rect for the requested item.
// The Rect for the requested item.
nsRect theRect;
nsPresContext* presContext = PresContext();
@ -4529,6 +4529,22 @@ nsTreeBodyFrame::ClearStyleAndImageCaches()
return NS_OK;
}
nsresult
nsTreeBodyFrame::RemoveImageCacheEntry(int32_t aRowIndex, nsITreeColumn* aCol)
{
nsAutoString imageSrc;
if (NS_SUCCEEDED(mView->GetImageSrc(aRowIndex, aCol, imageSrc))) {
nsTreeImageCacheEntry entry;
if (mImageCache.Get(imageSrc, &entry)) {
nsLayoutUtils::DeregisterImageRequest(PresContext(), entry.request,
nullptr);
entry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
mImageCache.Remove(imageSrc);
}
}
return NS_OK;
}
/* virtual */ void
nsTreeBodyFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
{

View File

@ -118,6 +118,7 @@ public:
nsresult BeginUpdateBatch();
nsresult EndUpdateBatch();
nsresult ClearStyleAndImageCaches();
nsresult RemoveImageCacheEntry(int32_t aRowIndex, nsITreeColumn* aCol);
void CancelImageRequests();

View File

@ -18,6 +18,7 @@ public class BackButton extends NavButton {
super.onSizeChanged(width, height, oldWidth, oldHeight);
mPath.reset();
mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);
mPath.addCircle(width / 2, height / 2, width / 2, Path.Direction.CW);
mBorderPath.reset();

View File

@ -260,18 +260,26 @@ public class GeckoView extends LayerView
init(context, newSettings);
}
private void init(Context context, final GeckoViewSettings settings) {
private void init(final Context context, final GeckoViewSettings settings) {
if (GeckoAppShell.getApplicationContext() == null) {
GeckoAppShell.setApplicationContext(context.getApplicationContext());
}
// Set the GeckoInterface if the context is an activity and the GeckoInterface
// has not already been set
// Set the GeckoInterface if the context is an activity and the
// GeckoInterface has not already been set
if (context instanceof Activity && getGeckoInterface() == null) {
setGeckoInterface(new BaseGeckoInterface(context));
GeckoAppShell.setContextGetter(this);
}
final GeckoProfile profile = GeckoProfile.get(
context.getApplicationContext());
if (GeckoThread.initMainProcess(profile,
/* args */ null,
/* debugging */ false)) {
GeckoThread.launch();
}
// Perform common initialization for Fennec/GeckoView.
GeckoAppShell.setLayerView(this);

View File

@ -11,13 +11,8 @@ import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import org.mozilla.gecko.BaseGeckoInterface;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.GeckoView;
import static org.mozilla.gecko.GeckoView.setGeckoInterface;
public class GeckoViewActivity extends Activity {
private static final String LOGTAG = "GeckoViewActivity";
private static final String DEFAULT_URL = "https://mozilla.org";
@ -30,8 +25,6 @@ public class GeckoViewActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setGeckoInterface(new BaseGeckoInterface(this));
setContentView(R.layout.geckoview_activity);
mGeckoView = (GeckoView) findViewById(R.id.gecko_view);
@ -42,11 +35,6 @@ public class GeckoViewActivity extends Activity {
prompt.filePickerRequestCode = REQUEST_FILE_PICKER;
mGeckoView.setPromptDelegate(prompt);
final GeckoProfile profile = GeckoProfile.get(this);
GeckoThread.initMainProcess(profile, /* args */ null, /* debugging */ false);
GeckoThread.launch();
loadFromIntent(getIntent());
}

View File

@ -61,4 +61,27 @@ this.TestUtils = {
}, topic);
});
},
/**
* Takes a screenshot of an area and returns it as a data URL.
*
* @param eltOrRect
* The DOM node or rect ({left, top, width, height}) to screenshot.
* @param win
* The current window.
*/
screenshotArea(eltOrRect, win) {
if (eltOrRect instanceof Ci.nsIDOMElement) {
eltOrRect = eltOrRect.getBoundingClientRect();
}
let { left, top, width, height } = eltOrRect;
let canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
let ctx = canvas.getContext("2d");
let ratio = win.devicePixelRatio;
canvas.width = width * ratio;
canvas.height = height * ratio;
ctx.scale(ratio, ratio);
ctx.drawWindow(win, left, top, width, height, "#fff");
return canvas.toDataURL();
}
};

View File

@ -284,6 +284,15 @@ class TabBase {
throw new Error("Not implemented");
}
/**
* @property {nsIFrameLoader} browser
* Returns the frameloader for the given tab.
* @readonly
*/
get frameLoader() {
return this.browser.frameLoader;
}
/**
* @property {string} cookieStoreId
* Returns the cookie store identifier for the given tab.
@ -454,9 +463,12 @@ class TabBase {
* of its properties which the extension is permitted to access, in the format
* requried to be returned by WebExtension APIs.
*
* @param {Tab} [fallbackTab]
* A tab to retrieve geometry data from if the lazy geometry data for
* this tab hasn't been initialized yet.
* @returns {object}
*/
convert() {
convert(fallbackTab = null) {
let result = {
id: this.id,
index: this.index,
@ -472,6 +484,13 @@ class TabBase {
mutedInfo: this.mutedInfo,
};
// If the tab has not been fully layed-out yet, fallback to the geometry
// from a different tab (usually the currently active tab).
if (fallbackTab && (!result.width || !result.height)) {
result.width = fallbackTab.width;
result.height = fallbackTab.height;
}
if (this.extension.hasPermission("cookies")) {
result.cookieStoreId = this.cookieStoreId;
}
@ -1632,11 +1651,15 @@ class TabManagerBase {
*
* @param {NativeTab} nativeTab
* The native tab to convert.
* @param {NativeTab} [fallbackTab]
* A tab to retrieve geometry data from if the lazy geometry data for
* this tab hasn't been initialized yet.
*
* @returns {Object}
*/
convert(nativeTab) {
return this.getWrapper(nativeTab).convert();
convert(nativeTab, fallbackTab = null) {
return this.getWrapper(nativeTab)
.convert(fallbackTab && this.getWrapper(fallbackTab));
}
// The JSDoc validator does not support @returns tags in abstract functions or

View File

@ -140,21 +140,19 @@ const URLTYPE_OPENSEARCH = "application/opensearchdescription+xml";
const BROWSER_SEARCH_PREF = "browser.search.";
const LOCALE_PREF = "general.useragent.locale";
const USER_DEFINED = "{searchTerms}";
const USER_DEFINED = "searchTerms";
// Custom search parameters
const MOZ_OFFICIAL = AppConstants.MOZ_OFFICIAL_BRANDING ? "official" : "unofficial";
const MOZ_PARAM_LOCALE = /\{moz:locale\}/g;
const MOZ_PARAM_DIST_ID = /\{moz:distributionID\}/g;
const MOZ_PARAM_OFFICIAL = /\{moz:official\}/g;
const MOZ_PARAM_LOCALE = "moz:locale";
const MOZ_PARAM_DIST_ID = "moz:distributionID"
const MOZ_PARAM_OFFICIAL = "moz:official";
// Supported OpenSearch parameters
// See http://opensearch.a9.com/spec/1.1/querysyntax/#core
const OS_PARAM_USER_DEFINED = /\{searchTerms\??\}/g;
const OS_PARAM_INPUT_ENCODING = /\{inputEncoding\??\}/g;
const OS_PARAM_LANGUAGE = /\{language\??\}/g;
const OS_PARAM_OUTPUT_ENCODING = /\{outputEncoding\??\}/g;
const OS_PARAM_USER_DEFINED = "searchTerms";
const OS_PARAM_INPUT_ENCODING = "inputEncoding";
const OS_PARAM_LANGUAGE = "language";
const OS_PARAM_OUTPUT_ENCODING = "outputEncoding";
// Default values
const OS_PARAM_LANGUAGE_DEF = "*";
@ -164,18 +162,15 @@ const OS_PARAM_INPUT_ENCODING_DEF = "UTF-8";
// "Unsupported" OpenSearch parameters. For example, we don't support
// page-based results, so if the engine requires that we send the "page index"
// parameter, we'll always send "1".
const OS_PARAM_COUNT = /\{count\??\}/g;
const OS_PARAM_START_INDEX = /\{startIndex\??\}/g;
const OS_PARAM_START_PAGE = /\{startPage\??\}/g;
const OS_PARAM_COUNT = "count";
const OS_PARAM_START_INDEX = "startIndex";
const OS_PARAM_START_PAGE = "startPage";
// Default values
const OS_PARAM_COUNT_DEF = "20"; // 20 results
const OS_PARAM_START_INDEX_DEF = "1"; // start at 1st result
const OS_PARAM_START_PAGE_DEF = "1"; // 1st page
// Optional parameter
const OS_PARAM_OPTIONAL = /\{(?:\w+:)?\w+\?\}/g;
// A array of arrays containing parameters that we don't fully support, and
// their default values. We will only send values for these parameters if
// required, since our values are just really arbitrary "guesses" that should
@ -989,44 +984,53 @@ function QueryParameter(aName, aValue, aPurpose) {
* @see http://opensearch.a9.com/spec/1.1/querysyntax/#core
*/
function ParamSubstitution(aParamValue, aSearchTerms, aEngine) {
var value = aParamValue;
const PARAM_REGEXP = /\{((?:\w+:)?\w+)(\??)\}/g;
return aParamValue.replace(PARAM_REGEXP, function(match, name, optional) {
// {searchTerms} is by far the most common param so handle it first.
if (name == USER_DEFINED)
return aSearchTerms;
var distributionID =
Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "distributionID",
Services.appinfo.distributionID || "");
// {inputEncoding} is the second most common param.
if (name == OS_PARAM_INPUT_ENCODING)
return aEngine.queryCharset;
var official;
if (Services.prefs.getBoolPref(BROWSER_SEARCH_PREF + "official", MOZ_OFFICIAL))
official = "official";
else
official = "unofficial";
// moz: parameters are only available for default search engines.
if (name.startsWith("moz:") && aEngine._isDefault) {
// {moz:locale} and {moz:distributionID} are common
if (name == MOZ_PARAM_LOCALE)
return getLocale();
if (name == MOZ_PARAM_DIST_ID) {
return Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "distributionID",
Services.appinfo.distributionID || "");
}
// {moz:official} seems to have little use.
if (name == MOZ_PARAM_OFFICIAL) {
if (Services.prefs.getBoolPref(BROWSER_SEARCH_PREF + "official",
AppConstants.MOZ_OFFICIAL_BRANDING))
return "official";
return "unofficial";
}
}
// Custom search parameters. These are only available to default search
// engines.
if (aEngine._isDefault) {
value = value.replace(MOZ_PARAM_LOCALE, getLocale());
value = value.replace(MOZ_PARAM_DIST_ID, distributionID);
value = value.replace(MOZ_PARAM_OFFICIAL, official);
}
// Handle the less common OpenSearch parameters we're confident about.
if (name == OS_PARAM_LANGUAGE)
return getLocale() || OS_PARAM_LANGUAGE_DEF;
if (name == OS_PARAM_OUTPUT_ENCODING)
return OS_PARAM_OUTPUT_ENCODING_DEF;
// Insert the OpenSearch parameters we're confident about
value = value.replace(OS_PARAM_USER_DEFINED, aSearchTerms);
value = value.replace(OS_PARAM_INPUT_ENCODING, aEngine.queryCharset);
value = value.replace(OS_PARAM_LANGUAGE,
getLocale() || OS_PARAM_LANGUAGE_DEF);
value = value.replace(OS_PARAM_OUTPUT_ENCODING,
OS_PARAM_OUTPUT_ENCODING_DEF);
// At this point, if a parameter is optional, just omit it.
if (optional)
return "";
// Replace any optional parameters
value = value.replace(OS_PARAM_OPTIONAL, "");
// Replace unsupported parameters that only have hardcoded default values.
for (let param of OS_UNSUPPORTED_PARAMS) {
if (name == param[0])
return param[1];
}
// Insert any remaining required params with our default values
for (var i = 0; i < OS_UNSUPPORTED_PARAMS.length; ++i) {
value = value.replace(OS_UNSUPPORTED_PARAMS[i][0],
OS_UNSUPPORTED_PARAMS[i][1]);
}
return value;
// Don't replace unknown non-optional parameters.
return match;
});
}
/**
@ -1148,11 +1152,11 @@ EngineURL.prototype = {
postData.setData(stringStream);
}
return new Submission(makeURI(url), postData);
return new Submission(Services.io.newURI(url), postData);
},
_getTermsParameterName: function SRCH_EURL__getTermsParameterName() {
let queryParam = this.params.find(p => p.value == USER_DEFINED);
let queryParam = this.params.find(p => p.value == "{" + USER_DEFINED + "}");
return queryParam ? queryParam.name : "";
},
@ -1495,9 +1499,9 @@ Engine.prototype = {
* @param aRel [optional] only return URLs that with this rel value
*/
_getURLOfType: function SRCH_ENG__getURLOfType(aType, aRel) {
for (var i = 0; i < this._urls.length; ++i) {
if (this._urls[i].type == aType && (!aRel || this._urls[i]._hasRelation(aRel)))
return this._urls[i];
for (let url of this._urls) {
if (url.type == aType && (!aRel || url._hasRelation(aRel)))
return url;
}
return null;

View File

@ -0,0 +1,67 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
function run_test() {
useHttpServer();
run_next_test();
}
add_task(function* test_paramSubstitution() {
yield asyncInit();
let prefix = "http://test.moz/search?q=";
let [engine] = yield addTestEngines([
{ name: "test", details: ["", "test", "Search Test", "GET",
prefix + "{searchTerms}"] },
]);
let url = engine.wrappedJSObject._getURLOfType("text/html");
equal(url.template, prefix + "{searchTerms}");
let searchTerms = "fxsearch";
function check(template, expected) {
url.template = prefix + template;
equal(engine.getSubmission(searchTerms).uri.spec, prefix + expected);
}
// The same parameter can be used more than once.
check("{searchTerms}/{searchTerms}", searchTerms + "/" + searchTerms);
// Optional parameters are replaced if we known them.
check("{searchTerms?}", searchTerms);
check("{unknownOptional?}", "");
check("{unknownRequired}", "{unknownRequired}");
check("{language}", Services.locale.getRequestedLocale());
check("{language?}", Services.locale.getRequestedLocale());
engine.wrappedJSObject._queryCharset = "UTF-8";
check("{inputEncoding}", "UTF-8");
check("{inputEncoding?}", "UTF-8");
check("{outputEncoding}", "UTF-8");
check("{outputEncoding?}", "UTF-8");
// 'Unsupported' parameters with hard coded values used only when the parameter is required.
check("{count}", "20");
check("{count?}", "");
check("{startIndex}", "1");
check("{startIndex?}", "");
check("{startPage}", "1");
check("{startPage?}", "");
// Test moz: parameters (only supported for built-in engines, ie _isDefault == true).
check("{moz:distributionID}", "{moz:distributionID}");
check("{moz:official}", "{moz:official}");
check("{moz:locale}", "{moz:locale}");
engine.wrappedJSObject._loadPath = "[app]"; // This will make _isDefault return true;
check("{moz:distributionID}", "");
Services.prefs.setCharPref("browser.search.distributionID", "xpcshell");
check("{moz:distributionID}", "xpcshell");
Services.prefs.setBoolPref("browser.search.official", true);
check("{moz:official}", "official");
Services.prefs.setBoolPref("browser.search.official", false);
check("{moz:official}", "unofficial");
check("{moz:locale}", Services.locale.getRequestedLocale());
});

View File

@ -99,3 +99,4 @@ tags = addons
[test_chromeresource_icon1.js]
[test_chromeresource_icon2.js]
[test_engineUpdate.js]
[test_paramSubstitution.js]

View File

@ -11318,12 +11318,12 @@
"description": "Measures the number of milliseconds we spend waiting for sync message manager IPC messages to finish sending, keyed by message name. Note: only messages that wait for more than 500 microseconds are included in this probe."
},
"DISPLAY_ITEM_USAGE_COUNT": {
"alert_emails": ["mchang@mozilla.com"],
"alert_emails": ["mchang@mozilla.com", "gfx-telemetry@mozilla.com"],
"bug_numbers": [1353521],
"expires_in_version": "70",
"expires_in_version": "56",
"kind": "enumerated",
"n_values": 99,
"description": "Count of which display items are being used by type id"
"description": "Count of which layout display items are being created. Display items are created by layout to determine what content to render. A full description is above the class definition for nsDisplayItem. The list of types is kept in nsDisplayItemTypes.h."
},
"TIME_TO_DOM_LOADING_MS": {
"alert_emails": ["wpan@mozilla.com"],

View File

@ -383,6 +383,7 @@ patched_SetUnhandledExceptionFilter (LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExce
return nullptr;
}
#ifdef _WIN64
static LPTOP_LEVEL_EXCEPTION_FILTER sUnhandledExceptionFilter = nullptr;
static long
@ -395,6 +396,15 @@ JitExceptionHandler(void *exceptionRecord, void *context)
return sUnhandledExceptionFilter(&pointers);
}
static void
SetJitExceptionHandler()
{
sUnhandledExceptionFilter = GetUnhandledExceptionFilter();
if (sUnhandledExceptionFilter)
js::SetJitExceptionHandler(JitExceptionHandler);
}
#endif
/**
* Reserve some VM space. In the event that we crash because VM space is
* being leaked without leaking memory, freeing this space before taking
@ -1801,9 +1811,7 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory,
#ifdef _WIN64
// Tell JS about the new filter before we disable SetUnhandledExceptionFilter
sUnhandledExceptionFilter = GetUnhandledExceptionFilter();
if (sUnhandledExceptionFilter)
js::SetJitExceptionHandler(JitExceptionHandler);
SetJitExceptionHandler();
#endif
// protect the crash reporter from being unloaded
@ -3807,6 +3815,10 @@ SetRemoteExceptionHandler(const nsACString& crashPipe)
nullptr);
gExceptionHandler->set_handle_debug_exceptions(true);
#ifdef _WIN64
SetJitExceptionHandler();
#endif
mozalloc_set_oom_abort_handler(AnnotateOOMAllocationSize);
oldTerminateHandler = std::set_terminate(&TerminateHandler);

View File

@ -9,6 +9,7 @@
#include "nsArrayUtils.h"
#include "nsClipboard.h"
#include "HeadlessClipboard.h"
#include "nsSupportsPrimitives.h"
#include "nsString.h"
#include "nsReadableUtils.h"
@ -20,6 +21,7 @@
#include "nsIObserverService.h"
#include "mozilla/Services.h"
#include "mozilla/RefPtr.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/TimeStamp.h"
#include "imgIContainer.h"
@ -38,6 +40,8 @@
#include "mozilla/dom/EncodingUtils.h"
#include "nsIUnicodeDecoder.h"
#include "gfxPlatform.h"
using mozilla::dom::EncodingUtils;
using namespace mozilla;
@ -79,6 +83,34 @@ selection_request_filter (GdkXEvent *gdk_xevent,
GdkEvent *event,
gpointer data);
namespace mozilla {
namespace clipboard {
StaticRefPtr<nsIClipboard> sInstance;
}
}
/* static */ already_AddRefed<nsIClipboard>
nsClipboard::GetInstance()
{
using namespace mozilla::clipboard;
if (!sInstance) {
if (gfxPlatform::IsHeadless()) {
sInstance = new widget::HeadlessClipboard();
} else {
RefPtr<nsClipboard> clipboard = new nsClipboard();
nsresult rv = clipboard->Init();
if (NS_FAILED(rv)) {
return nullptr;
}
sInstance = clipboard.forget();
}
ClearOnShutdown(&sInstance);
}
RefPtr<nsIClipboard> service = sInstance.get();
return service.forget();
}
nsClipboard::nsClipboard()
{
}

View File

@ -23,6 +23,8 @@ public:
NS_DECL_NSICLIPBOARD
NS_DECL_NSIOBSERVER
static already_AddRefed<nsIClipboard> GetInstance();
// Make sure we are initialized, called from the factory
// constructor
nsresult Init (void);

View File

@ -73,7 +73,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter)
#ifdef MOZ_X11
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceGTK, nsIdleServiceGTK::GetInstance)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsClipboard, Init)
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIClipboard, nsClipboard::GetInstance)
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsDragService, nsDragService::GetInstance)
#endif
NS_GENERIC_FACTORY_CONSTRUCTOR(nsSound)
@ -240,7 +240,7 @@ static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
{ &kNS_SOUND_CID, false, nullptr, nsSoundConstructor, Module::MAIN_PROCESS_ONLY },
{ &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor },
#ifdef MOZ_X11
{ &kNS_CLIPBOARD_CID, false, nullptr, nsClipboardConstructor, Module::MAIN_PROCESS_ONLY },
{ &kNS_CLIPBOARD_CID, false, nullptr, nsIClipboardConstructor, Module::MAIN_PROCESS_ONLY },
{ &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor },
{ &kNS_DRAGSERVICE_CID, false, nullptr, nsDragServiceConstructor, Module::MAIN_PROCESS_ONLY },
#endif

View File

@ -0,0 +1,126 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "HeadlessClipboard.h"
#include "nsISupportsPrimitives.h"
#include "nsComponentManagerUtils.h"
#include "nsCOMPtr.h"
namespace mozilla {
namespace widget {
NS_IMPL_ISUPPORTS(HeadlessClipboard, nsIClipboard)
HeadlessClipboard::HeadlessClipboard()
: mClipboard(MakeUnique<HeadlessClipboardData>())
{
}
NS_IMETHODIMP
HeadlessClipboard::SetData(nsITransferable *aTransferable,
nsIClipboardOwner *anOwner,
int32_t aWhichClipboard)
{
if (aWhichClipboard != kGlobalClipboard) {
return NS_ERROR_NOT_IMPLEMENTED;
}
// Clear out the clipboard in order to set the new data.
EmptyClipboard(aWhichClipboard);
// Only support plain text for now.
nsCOMPtr<nsISupports> clip;
uint32_t len;
nsresult rv = aTransferable->GetTransferData(kUnicodeMime,
getter_AddRefs(clip),
&len);
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsISupportsString> wideString = do_QueryInterface(clip);
if (!wideString) {
return NS_ERROR_NOT_IMPLEMENTED;
}
nsAutoString utf16string;
wideString->GetData(utf16string);
mClipboard->SetText(utf16string);
return NS_OK;
}
NS_IMETHODIMP
HeadlessClipboard::GetData(nsITransferable *aTransferable,
int32_t aWhichClipboard)
{
if (aWhichClipboard != kGlobalClipboard) {
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult rv;
nsCOMPtr<nsISupportsString> dataWrapper =
do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
rv = dataWrapper->SetData(mClipboard->GetText());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsISupports> genericDataWrapper = do_QueryInterface(dataWrapper);
uint32_t len = mClipboard->GetText().Length() * sizeof(char16_t);
rv = aTransferable->SetTransferData(kUnicodeMime, genericDataWrapper, len);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
HeadlessClipboard::EmptyClipboard(int32_t aWhichClipboard)
{
if (aWhichClipboard != kGlobalClipboard) {
return NS_ERROR_NOT_IMPLEMENTED;
}
mClipboard->Clear();
return NS_OK;
}
NS_IMETHODIMP
HeadlessClipboard::HasDataMatchingFlavors(const char **aFlavorList,
uint32_t aLength, int32_t aWhichClipboard,
bool *aHasType)
{
*aHasType = false;
if (aWhichClipboard != kGlobalClipboard) {
return NS_ERROR_NOT_IMPLEMENTED;
}
// Retrieve the union of all aHasType in aFlavorList
for (uint32_t i = 0; i < aLength; ++i) {
const char *flavor = aFlavorList[i];
if (!flavor) {
continue;
}
if (!strcmp(flavor, kUnicodeMime) && mClipboard->HasText()) {
*aHasType = true;
}
}
return NS_OK;
}
NS_IMETHODIMP
HeadlessClipboard::SupportsSelectionClipboard(bool *aIsSupported)
{
*aIsSupported = false;
return NS_OK;
}
NS_IMETHODIMP
HeadlessClipboard::SupportsFindClipboard(bool* _retval)
{
NS_ENSURE_ARG_POINTER(_retval);
*_retval = false;
return NS_OK;
}
} // namespace widget
} // namespace mozilla

View File

@ -0,0 +1,35 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* 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/. */
#ifndef mozilla_widget_HeadlessClipboard_h
#define mozilla_widget_HeadlessClipboard_h
#include "nsIClipboard.h"
#include "mozilla/UniquePtr.h"
#include "HeadlessClipboardData.h"
namespace mozilla {
namespace widget {
class HeadlessClipboard final : public nsIClipboard
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSICLIPBOARD
HeadlessClipboard();
protected:
~HeadlessClipboard() {}
private:
UniquePtr<HeadlessClipboardData> mClipboard;
};
} // namespace widget
} // namespace mozilla
#endif

View File

@ -0,0 +1,35 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "HeadlessClipboardData.h"
namespace mozilla {
namespace widget {
void
HeadlessClipboardData::SetText(const nsAString &aText)
{
mPlain = aText;
}
bool
HeadlessClipboardData::HasText() const
{
return !mPlain.IsEmpty();
}
const nsAString&
HeadlessClipboardData::GetText() const
{
return mPlain;
}
void
HeadlessClipboardData::Clear()
{
mPlain.Truncate(0);
}
} // namespace widget
} // namespace mozilla

View File

@ -0,0 +1,35 @@
/* 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/. */
#ifndef mozilla_widget_HeadlessClipboardData_h
#define mozilla_widget_HeadlessClipboardData_h
#include "mozilla/RefPtr.h"
#include "nsString.h"
namespace mozilla {
namespace widget {
class HeadlessClipboardData final
{
public:
explicit HeadlessClipboardData() = default;
~HeadlessClipboardData() = default;
// For text/plain
void SetText(const nsAString &aText);
bool HasText() const;
const nsAString& GetText() const;
// For other APIs
void Clear();
private:
nsAutoString mPlain;
};
} // namespace widget
} // namespace mozilla
#endif // mozilla_widget_HeadlessClipboardData_h

View File

@ -12,9 +12,12 @@ DIRS += ['tests']
LOCAL_INCLUDES += [
'/widget',
'/widget/gtk',
'/widget/headless',
]
UNIFIED_SOURCES += [
'HeadlessClipboard.cpp',
'HeadlessClipboardData.cpp',
'HeadlessLookAndFeel.cpp',
'HeadlessWidget.cpp',
]

View File

@ -0,0 +1,47 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
function getString(clipboard) {
var str = "";
// Create transferable that will transfer the text.
var trans = Cc["@mozilla.org/widget/transferable;1"]
.createInstance(Ci.nsITransferable);
trans.init(null);
trans.addDataFlavor("text/unicode");
clipboard.getData(trans, Ci.nsIClipboard.kGlobalClipboard);
try {
var data = {};
var dataLen = {};
trans.getTransferData("text/unicode", data, dataLen);
if (data) {
data = data.value.QueryInterface(Ci.nsISupportsString);
str = data.data.substring(0, dataLen.value / 2);
}
} catch (ex) {
// If the clipboard is empty getTransferData will throw.
}
return str;
}
add_task(function* test_clipboard() {
let clipboard = Cc['@mozilla.org/widget/clipboard;1']
.getService(Ci.nsIClipboard);
// Test copy.
const data = "random number: " + Math.random();
let helper = Cc['@mozilla.org/widget/clipboardhelper;1']
.getService(Ci.nsIClipboardHelper);
helper.copyString(data);
equal(getString(clipboard), data, 'Data was successfully copied.');
clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
equal(getString(clipboard), '', 'Data was successfully cleared.');
});

View File

@ -1,6 +1,9 @@
[test_headless.js]
[DEFAULT]
skip-if = os != "linux"
headless = true
[test_headless_clipboard.js]
[test_headless.js]
support-files =
headless.html
headless_button.html