Merge fx-team to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-10-28 16:02:50 -04:00
commit e9982eeaf4
57 changed files with 1427 additions and 578 deletions

View File

@ -56,7 +56,7 @@
<popupnotification id="pointerLock-notification" hidden="true">
<popupnotificationcontent orient="vertical" align="start">
<separator class="thin"/>
<label id="pointerLock-cancel" value="&pointerLock.notification.message;"/>
<label id="pointerLock-cancel">&pointerLock.notification.message;</label>
</popupnotificationcontent>
</popupnotification>

View File

@ -35,21 +35,31 @@ function runTests() {
expected.type = "sponsored";
expected.action = "view";
expected.pinned = false;
yield addNewTabPageTab();
yield null; // wait for reportSitesAction
addNewTabPageTab();
// Wait for addNewTabPageTab and reportSitesAction
yield null;
yield null;
// Click the pin button on the link in the 1th tile spot
let siteNode = getCell(1).node.querySelector(".newtab-site");
let pinButton = siteNode.querySelector(".newtab-control-pin");
expected.action = "pin";
expected.pinned = true;
yield EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
// Wait for reportSitesAction
yield null;
// Unpin that link
expected.action = "unpin";
expected.pinned = false;
yield EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
yield whenPagesUpdated();
whenPagesUpdated();
EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
// Wait for whenPagesUpdated and reportSitesAction
yield null;
yield null;
// Block the site in the 0th tile spot
let blockedSite = getCell(0).node.querySelector(".newtab-site");
@ -57,11 +67,18 @@ function runTests() {
expected.type = "organic";
expected.action = "block";
expected.pinned = false;
yield EventUtils.synthesizeMouseAtCenter(blockButton, {}, getContentWindow());
yield whenPagesUpdated();
whenPagesUpdated();
EventUtils.synthesizeMouseAtCenter(blockButton, {}, getContentWindow());
// Wait for whenPagesUpdated and reportSitesAction
yield null;
yield null;
// Click the 1th link now in the 0th tile spot
expected.type = "sponsored";
expected.action = "click";
yield EventUtils.synthesizeMouseAtCenter(siteNode, {}, getContentWindow());
EventUtils.synthesizeMouseAtCenter(siteNode, {}, getContentWindow());
// Wait for reportSitesAction
yield null;
}

View File

@ -109,20 +109,25 @@ let MozLoopPushHandler = {
+ (onRegistered ? "" : " onRegistered")
+ (onNotification ? "" : " onNotification"));
}
// Only register new channels
if (!(channelID in this.channels)) {
this.channels[channelID] = {
onRegistered: onRegistered,
onNotification: onNotification
};
// If registration is in progress, simply add to the work list.
// Else, re-start a registration cycle.
if (this._registrationID) {
this._channelsToRegister.push(channelID);
} else {
this._registerChannels();
}
// If the channel is already registered, callback with an error immediately
// so we don't leave code hanging waiting for an onRegistered callback.
if (channelID in this.channels) {
onRegistered("error: channel already registered: " + channelID);
return;
}
this.channels[channelID] = {
onRegistered: onRegistered,
onNotification: onNotification
};
// If registration is in progress, simply add to the work list.
// Else, re-start a registration cycle.
if (this._registrationID) {
this._channelsToRegister.push(channelID);
} else {
this._registerChannels();
}
},

View File

@ -46,8 +46,40 @@
Assert.equal(version, 15, "Should have version number 15");
Assert.equal(id, "chan-1", "Should have channel id = chan-1");
run_next_test();
});
});
add_test(function test_register_twice_same_channel() {
MozLoopPushHandler.register(
"chan-2",
function(err, url, id) {
Assert.equal(err, null, "Should return null for success");
Assert.equal(url, kEndPointUrl, "Should return push server application URL");
Assert.equal(id, "chan-2", "Should have channel id = chan-2");
Assert.equal(mockWebSocket.uri.prePath, kServerPushUrl,
"Should have the url from preferences");
Assert.equal(mockWebSocket.origin, kServerPushUrl,
"Should have the origin url from preferences");
Assert.equal(mockWebSocket.protocol, "push-notification",
"Should have the protocol set to push-notifications");
// Register again for the same channel
MozLoopPushHandler.register(
"chan-2",
function(err, url, id) {
Assert.notEqual(err, null, "Should have returned an error");
// Notify the first registration to make sure that still works.
mockWebSocket.notify(16);
},
function(version, id) {
Assert.ok(false, "The 2nd onNotification callback shouldn't be called");
});
},
mockWebSocket);
function(version, id) {
Assert.equal(version, 16, "Should have version number 16");
Assert.equal(id, "chan-2", "Should have channel id = chan-2");
run_next_test();
});
});
add_test(function test_reconnect_websocket() {

View File

@ -4,6 +4,8 @@ support-files = file_pdfjs_test.pdf
[browser_pdfjs_main.js]
skip-if = debug # bug 1058695
[browser_pdfjs_navigation.js]
skip-if = debug # bug 1058695
[browser_pdfjs_savedialog.js]
[browser_pdfjs_views.js]
skip-if = debug # bug 1058695

View File

@ -0,0 +1,206 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const RELATIVE_DIR = "browser/extensions/pdfjs/test/";
const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
const PDF_OUTLINE_ITEMS = 17;
const TESTS = [
{
action: {
selector: "button#next",
event: "click"
},
expectedPage: 2,
message: "navigated to next page using NEXT button"
},
{
action: {
selector: "button#previous",
event: "click"
},
expectedPage: 1,
message: "navigated to previous page using PREV button"
},
{
action: {
selector: "button#next",
event: "click"
},
expectedPage: 2,
message: "navigated to next page using NEXT button"
},
{
action: {
selector: "input#pageNumber",
value: 1,
event: "change"
},
expectedPage: 1,
message: "navigated to first page using pagenumber"
},
{
action: {
selector: "#thumbnailView a:nth-child(4)",
event: "click"
},
expectedPage: 4,
message: "navigated to 4th page using thumbnail view"
},
{
action: {
selector: "#thumbnailView a:nth-child(2)",
event: "click"
},
expectedPage: 2,
message: "navigated to 2nd page using thumbnail view"
},
{
action: {
selector: ".outlineItem:nth-child(1) a",
event: "click"
},
expectedPage: 1,
message: "navigated to 1st page using outline view"
},
{
action: {
selector: ".outlineItem:nth-child(" + PDF_OUTLINE_ITEMS + ") a",
event: "click"
},
expectedPage: 4,
message: "navigated to 4th page using outline view"
},
{
action: {
selector: "input#pageNumber",
value: 5,
event: "change"
},
expectedPage: 5,
message: "navigated to 5th page using pagenumber"
}
];
function test() {
var tab;
let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
let handlerInfo = mimeService.getFromTypeAndExtension('application/pdf', 'pdf');
// Make sure pdf.js is the default handler.
is(handlerInfo.alwaysAskBeforeHandling, false, 'pdf handler defaults to always-ask is false');
is(handlerInfo.preferredAction, Ci.nsIHandlerInfo.handleInternally, 'pdf handler defaults to internal');
info('Pref action: ' + handlerInfo.preferredAction);
waitForExplicitFinish();
registerCleanupFunction(function() {
gBrowser.removeTab(tab);
});
tab = gBrowser.addTab(TESTROOT + "file_pdfjs_test.pdf");
gBrowser.selectedTab = tab;
var newTabBrowser = gBrowser.getBrowserForTab(tab);
newTabBrowser.addEventListener("load", function eventHandler() {
newTabBrowser.removeEventListener("load", eventHandler, true);
var document = newTabBrowser.contentDocument,
window = newTabBrowser.contentWindow;
// Runs tests after all 'load' event handlers have fired off
window.addEventListener("documentload", function() {
runTests(document, window, function () {
var pageNumber = document.querySelector('input#pageNumber');
is(pageNumber.value, pageNumber.max, "Document is left on the last page");
finish();
});
}, false, true);
}, true);
}
function runTests(document, window, finish) {
// Check if PDF is opened with internal viewer
ok(document.querySelector('div#viewer'), "document content has viewer UI");
ok('PDFJS' in window.wrappedJSObject, "window content has PDFJS object");
// Wait for outline items, the start the navigation actions
waitForOutlineItems(document).then(function () {
runNextTest(document, window, finish);
}, function () {
ok(false, "Outline items have ben found");
finish();
});
}
/**
* As the page changes asynchronously, we have to wait for the event after
* we trigger the action so we will be at the expected page number after each action
*
* @param document
* @param window
* @param test
* @param callback
*/
function runNextTest(document, window, endCallback) {
var test = TESTS.shift(),
deferred = Promise.defer(),
pageNumber = document.querySelector('input#pageNumber');
// Add an event-listener to wait for page to change, afterwards resolve the promise
var timeout = window.setTimeout(() => deferred.reject(), 5000);
window.addEventListener('pagechange', function pageChange() {
if (pageNumber.value == test.expectedPage) {
window.removeEventListener('pagechange', pageChange);
window.clearTimeout(timeout);
deferred.resolve(pageNumber.value);
}
});
// Get the element and trigger the action for changing the page
var el = document.querySelector(test.action.selector);
ok(el, "Element '" + test.action.selector + "' has been found");
// The value option is for input case
if (test.action.value)
el.value = test.action.value;
// Dispatch the event for changing the page
el.dispatchEvent(new Event(test.action.event));
// When the promise gets resolved we call the next test if there are any left
// or else we call the final callback which will end the test
deferred.promise.then(function (pgNumber) {
is(pgNumber, test.expectedPage, test.message);
if (TESTS.length)
runNextTest(document, window, endCallback);
else
endCallback();
}, function () {
ok(false, "Test '" + test.message + "' failed with timeout.");
endCallback();
});
}
/**
* Outline Items gets appended to the document latter on we have to
* wait for them before we start to navigate though document
*
* @param document
* @returns {deferred.promise|*}
*/
function waitForOutlineItems(document) {
var deferred = Promise.defer();
var timeout = setTimeout(() => deferred.reject(), 10000);
var interval = setInterval(function () {
if (document.querySelectorAll(".outlineItem").length == PDF_OUTLINE_ITEMS) {
clearInterval(interval);
clearTimeout(timeout);
deferred.resolve();
}
}, 500);
return deferred.promise;
}

View File

@ -0,0 +1,139 @@
/* -*- Mode: C++; tab-width: 4; 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/. */
#include "AnonymousContent.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/AnonymousContentBinding.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIDocument.h"
#include "nsIDOMHTMLCollection.h"
#include "nsStyledElement.h"
namespace mozilla {
namespace dom {
// Ref counting and cycle collection
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AnonymousContent, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AnonymousContent, Release)
NS_IMPL_CYCLE_COLLECTION(AnonymousContent, mContentNode)
AnonymousContent::AnonymousContent(Element* aContentNode) :
mContentNode(aContentNode)
{}
AnonymousContent::~AnonymousContent()
{
}
nsCOMPtr<Element>
AnonymousContent::GetContentNode()
{
return mContentNode;
}
void
AnonymousContent::SetContentNode(Element* aContentNode)
{
mContentNode = aContentNode;
}
void
AnonymousContent::SetTextContentForElement(const nsAString& aElementId,
const nsAString& aText,
ErrorResult& aRv)
{
Element* element = GetElementById(aElementId);
if (!element) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return;
}
element->SetTextContent(aText, aRv);
}
void
AnonymousContent::GetTextContentForElement(const nsAString& aElementId,
DOMString& aText,
ErrorResult& aRv)
{
Element* element = GetElementById(aElementId);
if (!element) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return;
}
element->GetTextContent(aText, aRv);
}
void
AnonymousContent::SetAttributeForElement(const nsAString& aElementId,
const nsAString& aName,
const nsAString& aValue,
ErrorResult& aRv)
{
Element* element = GetElementById(aElementId);
if (!element) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return;
}
element->SetAttribute(aName, aValue, aRv);
}
void
AnonymousContent::GetAttributeForElement(const nsAString& aElementId,
const nsAString& aName,
DOMString& aValue,
ErrorResult& aRv)
{
Element* element = GetElementById(aElementId);
if (!element) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return;
}
element->GetAttribute(aName, aValue);
}
void
AnonymousContent::RemoveAttributeForElement(const nsAString& aElementId,
const nsAString& aName,
ErrorResult& aRv)
{
Element* element = GetElementById(aElementId);
if (!element) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return;
}
element->RemoveAttribute(aName, aRv);
}
Element*
AnonymousContent::GetElementById(const nsAString& aElementId)
{
// This can be made faster in the future if needed.
nsCOMPtr<nsIAtom> elementId = do_GetAtom(aElementId);
for (nsIContent* kid = mContentNode->GetFirstChild(); kid;
kid = kid->GetNextNode(mContentNode)) {
if (!kid->IsElement()) {
continue;
}
nsIAtom* id = kid->AsElement()->GetID();
if (id && id == elementId) {
return kid->AsElement();
}
}
return nullptr;
}
JSObject*
AnonymousContent::WrapObject(JSContext* aCx)
{
return AnonymousContentBinding::Wrap(aCx, this);
}
} // dom namespace
} // mozilla namespace

View File

@ -0,0 +1,64 @@
/* -*- Mode: C++; tab-width: 3; 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_dom_AnonymousContent_h
#define mozilla_dom_AnonymousContent_h
#include "mozilla/dom/Element.h"
#include "nsCycleCollectionParticipant.h"
#include "nsICSSDeclaration.h"
#include "nsIDocument.h"
namespace mozilla {
namespace dom {
class Element;
class AnonymousContent MOZ_FINAL
{
public:
// Ref counting and cycle collection
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AnonymousContent)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AnonymousContent)
explicit AnonymousContent(Element* aContentNode);
nsCOMPtr<Element> GetContentNode();
void SetContentNode(Element* aContentNode);
JSObject* WrapObject(JSContext* aCx);
// WebIDL methods
void SetTextContentForElement(const nsAString& aElementId,
const nsAString& aText,
ErrorResult& aRv);
void GetTextContentForElement(const nsAString& aElementId,
DOMString& aText,
ErrorResult& aRv);
void SetAttributeForElement(const nsAString& aElementId,
const nsAString& aName,
const nsAString& aValue,
ErrorResult& aRv);
void GetAttributeForElement(const nsAString& aElementId,
const nsAString& aName,
DOMString& aValue,
ErrorResult& aRv);
void RemoveAttributeForElement(const nsAString& aElementId,
const nsAString& aName,
ErrorResult& aRv);
private:
~AnonymousContent();
Element* GetElementById(const nsAString& aElementId);
nsCOMPtr<Element> mContentNode;
};
} // dom namespace
} // mozilla namespace
#endif // mozilla_dom_AnonymousContent_h

View File

@ -142,6 +142,7 @@ EXPORTS.mozilla += [
]
EXPORTS.mozilla.dom += [
'AnonymousContent.h',
'Attr.h',
'BarProps.h',
'BlobSet.h',
@ -196,6 +197,7 @@ EXPORTS.mozilla.dom += [
]
UNIFIED_SOURCES += [
'AnonymousContent.cpp',
'Attr.cpp',
'BarProps.cpp',
'ChildIterator.cpp',

View File

@ -39,6 +39,7 @@
#include "mozilla/dom/HTMLTemplateElement.h"
#include "mozilla/dom/HTMLContentElement.h"
#include "mozilla/dom/HTMLShadowElement.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/TextDecoder.h"
#include "mozilla/dom/TouchEvent.h"
@ -5081,7 +5082,7 @@ nsContentUtils::LeaveMicroTask()
{
MOZ_ASSERT(NS_IsMainThread());
if (--sMicroTaskLevel == 0) {
nsDOMMutationObserver::HandleMutations();
PerformMainThreadMicroTaskCheckpoint();
nsDocument::ProcessBaseElementQueue();
}
}
@ -5107,6 +5108,14 @@ nsContentUtils::SetMicroTaskLevel(uint32_t aLevel)
sMicroTaskLevel = aLevel;
}
void
nsContentUtils::PerformMainThreadMicroTaskCheckpoint()
{
MOZ_ASSERT(NS_IsMainThread());
nsDOMMutationObserver::HandleMutations();
}
/*
* Helper function for nsContentUtils::ProcessViewportInfo.
*

View File

@ -1542,6 +1542,8 @@ public:
static uint32_t MicroTaskLevel();
static void SetMicroTaskLevel(uint32_t aLevel);
static void PerformMainThreadMicroTaskCheckpoint();
/* Process viewport META data. This gives us information for the scale
* and zoom of a page on mobile devices. We stick the information in
* the document header and use it later on after rendering.

View File

@ -68,6 +68,7 @@
#include "nsIServiceManager.h"
#include "nsIServiceWorkerManager.h"
#include "nsCanvasFrame.h"
#include "nsContentCID.h"
#include "nsError.h"
#include "nsPresShell.h"
@ -182,6 +183,7 @@
#include "nsWrapperCacheInlines.h"
#include "nsSandboxFlags.h"
#include "nsIAppsService.h"
#include "mozilla/dom/AnonymousContent.h"
#include "mozilla/dom/AnimationTimeline.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DocumentFragment.h"
@ -1986,6 +1988,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRegistry)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
// Traverse all our nsCOMArrays.
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets)
@ -5138,6 +5141,78 @@ nsDocument::StyleRuleRemoved(nsIStyleSheet* aSheet,
#undef DO_STYLESHEET_NOTIFICATION
already_AddRefed<AnonymousContent>
nsIDocument::InsertAnonymousContent(Element& aElement, ErrorResult& aRv)
{
nsIPresShell* shell = GetShell();
if (!shell || !shell->GetCanvasFrame()) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
nsCOMPtr<Element> container = shell->GetCanvasFrame()
->GetCustomContentContainer();
if (!container) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
// Clone the node to avoid returning a direct reference
nsCOMPtr<nsINode> clonedElement = aElement.CloneNode(true, aRv);
if (aRv.Failed()) {
return nullptr;
}
// Insert the element into the container
nsresult rv;
rv = container->AppendChildTo(clonedElement->AsContent(), true);
if (NS_FAILED(rv)) {
return nullptr;
}
nsRefPtr<AnonymousContent> anonymousContent =
new AnonymousContent(clonedElement->AsElement());
mAnonymousContents.AppendElement(anonymousContent);
return anonymousContent.forget();
}
void
nsIDocument::RemoveAnonymousContent(AnonymousContent& aContent,
ErrorResult& aRv)
{
nsIPresShell* shell = GetShell();
if (!shell) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
nsCOMPtr<Element> container = shell->GetCanvasFrame()
->GetCustomContentContainer();
if (!container) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
// Iterate over know customContents to get and remove the right one
for (int32_t i = mAnonymousContents.Length() - 1; i >= 0; --i) {
if (mAnonymousContents[i] == &aContent) {
// Get the node from the customContent
nsCOMPtr<Element> node = aContent.GetContentNode();
// Remove the entry in mAnonymousContents
mAnonymousContents.RemoveElementAt(i);
// Remove the node from its container
container->RemoveChild(*node, aRv);
if (aRv.Failed()) {
return;
}
break;
}
}
}
//
// nsIDOMDocument interface

View File

@ -92,6 +92,7 @@ class ImageLoader;
namespace dom {
class AnimationTimeline;
class AnonymousContent;
class Attr;
class BoxObject;
class CDATASection;
@ -715,6 +716,15 @@ public:
return mDidDocumentOpen;
}
already_AddRefed<mozilla::dom::AnonymousContent>
InsertAnonymousContent(mozilla::dom::Element& aElement,
mozilla::ErrorResult& aError);
void RemoveAnonymousContent(mozilla::dom::AnonymousContent& aContent,
mozilla::ErrorResult& aError);
nsTArray<nsRefPtr<mozilla::dom::AnonymousContent>>& GetAnonymousContents() {
return mAnonymousContents;
}
protected:
virtual Element *GetRootElementInternal() const = 0;
@ -2747,6 +2757,8 @@ protected:
nsRefPtr<mozilla::dom::XPathEvaluator> mXPathEvaluator;
nsTArray<nsRefPtr<mozilla::dom::AnonymousContent>> mAnonymousContents;
uint32_t mBlockDOMContentLoaded;
bool mDidFireDOMContentLoaded:1;
};

View File

@ -3,6 +3,7 @@ support-files =
file_url.jsm
file_empty.html
[test_anonymousContent_xul_window.xul]
[test_bug715041.xul]
[test_bug715041_removal.xul]
[test_domrequesthelper.xul]

View File

@ -239,6 +239,10 @@ support-files =
w3element_traversal.svg
wholeTexty-helper.xml
[test_anonymousContent_api.html]
[test_anonymousContent_append_after_reflow.html]
[test_anonymousContent_insert.html]
[test_anonymousContent_manipulate_content.html]
[test_appname_override.html]
[test_audioWindowUtils.html]
[test_audioNotification.html]

View File

@ -0,0 +1,55 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1020244
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1020244 - Test the chrome-only AnonymousContent API</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020244">Mozilla Bug 1020244</a>
<script type="application/javascript;version=1.8">
// Testing the presence of the chrome-only API
ok(!document.insertAnonymousContent,
"Content document shouldn't have access to insertAnonymousContent");
ok(!document.removeAnonymousContent,
"Content document shouldn't have access to removeAnonymousContent");
let chromeDocument = SpecialPowers.wrap(document);
ok(chromeDocument.insertAnonymousContent,
"Chrome document should have access to insertAnonymousContent");
ok(chromeDocument.removeAnonymousContent,
"Chrome document should have access to removeAnonymousContent");
// Testing invalid inputs
let invalidNodes = [null, undefined, false, 1, "string"];
for (let node of invalidNodes) {
let didThrow = false;
try {
chromeDocument.insertAnonymousContent(node);
} catch (e) {
didThrow = true;
}
ok(didThrow, "Passing an invalid node to insertAnonymousContent should throw");
}
// Testing the API of the returned object
let div = document.createElement("div");
div.textContent = "this is a test element";
let anonymousContent = chromeDocument.insertAnonymousContent(div);
ok(anonymousContent, "AnonymousContent object returned");
let members = ["getTextContentForElement", "setTextContentForElement",
"getAttributeForElement", "setAttributeForElement",
"removeAttributeForElement"];
for (let member of members) {
ok(member in anonymousContent, "AnonymousContent object defines " + member);
}
chromeDocument.removeAnonymousContent(anonymousContent);
</script>
</body>
</html>

View File

@ -0,0 +1,40 @@
<!DOCTYPE HTML>
<html>
<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1020244 -->
<head>
<meta charset="utf-8">
<title>Test for Bug 1020244 - Make sure anonymous content still works after a reflow (after the canvasframe has been reconstructed)</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<div>
<div id="test-element" style="color:red;">text content</div>
</div>
<script type="application/javascript;version=1.8">
info("Inserting anonymous content into the document frame");
let chromeDocument = SpecialPowers.wrap(document);
let testElement = document.querySelector("div");
let anonymousContent = chromeDocument.insertAnonymousContent(testElement);
info("Modifying the style of an element in the anonymous content");
let style = anonymousContent.setAttributeForElement("test-element",
"style", "color:green;");
info("Toggling the display style on the document element to force reframing");
// Note that we force sync reflows to make sure the canvasframe is recreated
// synchronously.
document.documentElement.style.display = "none";
let forceFlush = document.documentElement.offsetHeight;
document.documentElement.style.display = "block";
forceFlush = document.documentElement.offsetHeight;
info("Checking that the anonymous content can be retrieved still");
let style = anonymousContent.getAttributeForElement("test-element", "style");
is(style, "color:green;", "The anonymous content still exists after reflow");
info("Removing the anonymous content");
chromeDocument.removeAnonymousContent(anonymousContent);
</script>
</body>
</html>

View File

@ -0,0 +1,45 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1020244
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1020244 - Insert content using the AnonymousContent API, several times, and don't remove it</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020244">Mozilla Bug 1020244</a>
<div>
<div id="id" class="test">text content</div>
</div>
<script type="application/javascript;version=1.8">
const INSERTED_NB = 5;
// Insert the same content several times
let chromeDocument = SpecialPowers.wrap(document);
let testElement = document.querySelector("div");
let anonymousContents = [];
for (let i = 0; i < INSERTED_NB; i ++) {
let content = chromeDocument.insertAnonymousContent(testElement);
// Adding an expando pointing to the document to make sure this doesn't
// cause leaks.
content.dummyExpando = testElement.ownerDocument;
anonymousContents.push(content);
}
// Make sure that modifying one of the inserted elements does not modify the
// other ones.
anonymousContents[0].setAttributeForElement("id", "class", "updated");
for (let i = 1; i < INSERTED_NB; i ++) {
is(anonymousContents[i].getAttributeForElement("id", "class"),
"test",
"Element " + i + " didn't change when element 0 was changed");
}
// Do not remove inserted elements on purpose to test for potential leaks too.
</script>
</body>
</html>

View File

@ -0,0 +1,66 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1020244
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1020244 - Manipulate content created with the AnonymousContent API</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020244">Mozilla Bug 1020244</a>
<div>
<div id="test-element" class="test-class" test="test">text content</div>
</div>
<script type="application/javascript;version=1.8">
// Insert content
let chromeDocument = SpecialPowers.wrap(document);
let testElement = document.querySelector("div");
let anonymousContent = chromeDocument.insertAnonymousContent(testElement);
// Test getting/setting text content.
is(anonymousContent.getTextContentForElement("test-element"),
"text content", "Textcontent for the test element is correct");
anonymousContent.setTextContentForElement("test-element",
"updated text content");
is(anonymousContent.getTextContentForElement("test-element"),
"updated text content",
"Textcontent for the test element is correct after update");
// Test that modifying the original DOM element doesn't change the inserted
// element.
testElement.removeAttribute("test");
is(anonymousContent.getAttributeForElement("test-element", "test"),
"test",
"Removing attributes on the original DOM node does not change the inserted node");
testElement.setAttribute("test", "test-updated");
is(anonymousContent.getAttributeForElement("test-element", "test"),
"test",
"Setting attributes on the original DOM node does not change the inserted node");
// Test getting/setting/removing attributes on the inserted element and test
// that this doesn't change the original DOM element.
is(anonymousContent.getAttributeForElement("test-element", "class"),
"test-class", "Class attribute for the test element is correct");
anonymousContent.setAttributeForElement("test-element", "class",
"updated-test-class");
is(anonymousContent.getAttributeForElement("test-element", "class"),
"updated-test-class",
"Class attribute for the test element is correct after update");
ok(testElement.getAttribute("class") !== "updated-test-class",
"Class attribute change on the inserted node does not change the original DOM node");
anonymousContent.removeAttributeForElement("test-element", "class");
is(anonymousContent.getAttributeForElement("test-element", "class"), null,
"Class attribute for the test element was removed");
chromeDocument.removeAnonymousContent(anonymousContent);
</script>
</body>
</html>

View File

@ -0,0 +1,30 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1020244
Check that XUL windows aren't supported, that the API throws, but doesn't crash.
-->
<window title="Anonymous content in a XUL window"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml"></body>
<box>This is a test box</box>
<script type="application/javascript;version=1.8">
// Insert content
let testElement = document.querySelector("box");
let didThrow = false;
try {
document.insertAnonymousContent(testElement);
} catch (e) {
didThrow = true;
}
ok(didThrow,
"Inserting anonymous content in a XUL document did throw an exception")
</script>
</window>

View File

@ -101,6 +101,10 @@ DOMInterfaces = {
'concrete': False
},
'AnonymousContent': {
'wrapperCache': False
},
'ArchiveReader': {
'nativeType': 'mozilla::dom::archivereader::ArchiveReader',
},

View File

@ -100,7 +100,7 @@ AbortablePromise::Abort()
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this, &AbortablePromise::DoAbort);
Promise::DispatchToMainOrWorkerThread(runnable);
Promise::DispatchToMicroTask(runnable);
}
void

View File

@ -35,78 +35,55 @@ using namespace workers;
NS_IMPL_ISUPPORTS0(PromiseNativeHandler)
// PromiseTask
// This class processes the promise's callbacks with promise's result.
class PromiseTask MOZ_FINAL : public nsRunnable
class PromiseCallbackTask MOZ_FINAL : public nsRunnable
{
public:
explicit PromiseTask(Promise* aPromise)
: mPromise(aPromise)
{
MOZ_ASSERT(aPromise);
MOZ_COUNT_CTOR(PromiseTask);
}
protected:
~PromiseTask()
{
NS_ASSERT_OWNINGTHREAD(PromiseTask);
MOZ_COUNT_DTOR(PromiseTask);
}
public:
NS_IMETHOD
Run() MOZ_OVERRIDE
{
NS_ASSERT_OWNINGTHREAD(PromiseTask);
mPromise->mTaskPending = false;
mPromise->RunTask();
return NS_OK;
}
private:
nsRefPtr<Promise> mPromise;
NS_DECL_OWNINGTHREAD
};
// This class processes the promise's callbacks with promise's result.
class PromiseResolverTask MOZ_FINAL : public nsRunnable
{
public:
PromiseResolverTask(Promise* aPromise,
JS::Handle<JS::Value> aValue,
Promise::PromiseState aState)
PromiseCallbackTask(Promise* aPromise,
PromiseCallback* aCallback,
const JS::Value& aValue)
: mPromise(aPromise)
, mCallback(aCallback)
, mValue(CycleCollectedJSRuntime::Get()->Runtime(), aValue)
, mState(aState)
{
MOZ_ASSERT(aPromise);
MOZ_ASSERT(mState != Promise::Pending);
MOZ_COUNT_CTOR(PromiseResolverTask);
MOZ_ASSERT(aCallback);
MOZ_COUNT_CTOR(PromiseCallbackTask);
}
virtual
~PromiseResolverTask()
~PromiseCallbackTask()
{
NS_ASSERT_OWNINGTHREAD(PromiseResolverTask);
MOZ_COUNT_DTOR(PromiseResolverTask);
NS_ASSERT_OWNINGTHREAD(PromiseCallbackTask);
MOZ_COUNT_DTOR(PromiseCallbackTask);
}
protected:
NS_IMETHOD
Run() MOZ_OVERRIDE
{
NS_ASSERT_OWNINGTHREAD(PromiseResolverTask);
mPromise->RunResolveTask(
JS::Handle<JS::Value>::fromMarkedLocation(mValue.address()),
mState, Promise::SyncTask);
NS_ASSERT_OWNINGTHREAD(PromiseCallbackTask);
ThreadsafeAutoJSContext cx;
JS::Rooted<JSObject*> wrapper(cx, mPromise->GetWrapper());
MOZ_ASSERT(wrapper); // It was preserved!
JSAutoCompartment ac(cx, wrapper);
JS::Rooted<JS::Value> value(cx, mValue);
if (!MaybeWrapValue(cx, &value)) {
NS_WARNING("Failed to wrap value into the right compartment.");
JS_ClearPendingException(cx);
return NS_OK;
}
mCallback->Call(cx, value);
return NS_OK;
}
private:
nsRefPtr<Promise> mPromise;
nsRefPtr<PromiseCallback> mCallback;
JS::PersistentRooted<JS::Value> mValue;
Promise::PromiseState mState;
NS_DECL_OWNINGTHREAD;
};
@ -202,9 +179,6 @@ protected:
ThreadsafeAutoJSContext cx;
JS::Rooted<JSObject*> wrapper(cx, mPromise->GetWrapper());
MOZ_ASSERT(wrapper); // It was preserved!
if (!wrapper) {
return NS_OK;
}
JSAutoCompartment ac(cx, wrapper);
JS::Rooted<JSObject*> resolveFunc(cx,
@ -247,7 +221,7 @@ protected:
NS_WARNING("Failed to wrap value into the right compartment.");
}
mPromise->RejectInternal(cx, exn, Promise::SyncTask);
mPromise->RejectInternal(cx, exn);
}
// At least one of resolveFunc or rejectFunc have been called, so ignore
// the exception. FIXME(nsm): This should be reported to the error
@ -306,7 +280,6 @@ Promise::Promise(nsIGlobalObject* aGlobal)
, mRejectionStack(nullptr)
, mFullfillmentStack(nullptr)
, mState(Pending)
, mTaskPending(false)
, mHadRejectCallback(false)
, mResolvePending(false)
{
@ -381,6 +354,23 @@ Promise::MaybeReject(JSContext* aCx,
MaybeRejectInternal(aCx, aValue);
}
void
Promise::PerformMicroTaskCheckpoint()
{
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
nsTArray<nsRefPtr<nsIRunnable>>& microtaskQueue =
runtime->GetPromiseMicroTaskQueue();
while (!microtaskQueue.IsEmpty()) {
nsRefPtr<nsIRunnable> runnable = microtaskQueue.ElementAt(0);
MOZ_ASSERT(runnable);
// This function can re-enter, so we remove the element before calling.
microtaskQueue.RemoveElementAt(0);
runnable->Run();
}
}
/* static */ bool
Promise::JSCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
{
@ -941,13 +931,11 @@ Promise::AppendCallbacks(PromiseCallback* aResolveCallback,
RemoveFeature();
}
// If promise's state is resolved, queue a task to process our resolve
// If promise's state is fulfilled, queue a task to process our fulfill
// callbacks with promise's result. If promise's state is rejected, queue a
// task to process our reject callbacks with promise's result.
if (mState != Pending && !mTaskPending) {
nsRefPtr<PromiseTask> task = new PromiseTask(this);
DispatchToMainOrWorkerThread(task);
mTaskPending = true;
if (mState != Pending) {
EnqueueCallbackTasks();
}
}
@ -983,43 +971,15 @@ private:
};
/* static */ void
Promise::DispatchToMainOrWorkerThread(nsIRunnable* aRunnable)
Promise::DispatchToMicroTask(nsIRunnable* aRunnable)
{
MOZ_ASSERT(aRunnable);
if (NS_IsMainThread()) {
NS_DispatchToCurrentThread(aRunnable);
return;
}
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(worker);
nsRefPtr<WrappedWorkerRunnable> task = new WrappedWorkerRunnable(worker, aRunnable);
task->Dispatch(worker->GetJSContext());
}
void
Promise::RunTask()
{
MOZ_ASSERT(mState != Pending);
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
nsTArray<nsRefPtr<nsIRunnable>>& microtaskQueue =
runtime->GetPromiseMicroTaskQueue();
nsTArray<nsRefPtr<PromiseCallback>> callbacks;
callbacks.SwapElements(mState == Resolved ? mResolveCallbacks
: mRejectCallbacks);
mResolveCallbacks.Clear();
mRejectCallbacks.Clear();
ThreadsafeAutoJSContext cx;
JS::Rooted<JS::Value> value(cx, mResult);
JS::Rooted<JSObject*> wrapper(cx, GetWrapper());
MOZ_ASSERT(wrapper); // We preserved it
JSAutoCompartment ac(cx, wrapper);
if (!MaybeWrapValue(cx, &value)) {
return;
}
for (uint32_t i = 0; i < callbacks.Length(); ++i) {
callbacks[i]->Call(cx, value);
}
microtaskQueue.AppendElement(aRunnable);
}
void
@ -1069,26 +1029,24 @@ Promise::MaybeReportRejected()
void
Promise::MaybeResolveInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aAsynchronous)
JS::Handle<JS::Value> aValue)
{
if (mResolvePending) {
return;
}
ResolveInternal(aCx, aValue, aAsynchronous);
ResolveInternal(aCx, aValue);
}
void
Promise::MaybeRejectInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aAsynchronous)
JS::Handle<JS::Value> aValue)
{
if (mResolvePending) {
return;
}
RejectInternal(aCx, aValue, aAsynchronous);
RejectInternal(aCx, aValue);
}
void
@ -1097,14 +1055,13 @@ Promise::HandleException(JSContext* aCx)
JS::Rooted<JS::Value> exn(aCx);
if (JS_GetPendingException(aCx, &exn)) {
JS_ClearPendingException(aCx);
RejectInternal(aCx, exn, SyncTask);
RejectInternal(aCx, exn);
}
}
void
Promise::ResolveInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aAsynchronous)
JS::Handle<JS::Value> aValue)
{
mResolvePending = true;
@ -1126,45 +1083,27 @@ Promise::ResolveInternal(JSContext* aCx,
new PromiseInit(thenObj, mozilla::dom::GetIncumbentGlobal());
nsRefPtr<ThenableResolverTask> task =
new ThenableResolverTask(this, valueObj, thenCallback);
DispatchToMainOrWorkerThread(task);
DispatchToMicroTask(task);
return;
}
}
// If the synchronous flag is set, process our resolve callbacks with
// value. Otherwise, the synchronous flag is unset, queue a task to process
// own resolve callbacks with value. Otherwise, the synchronous flag is
// unset, queue a task to process our resolve callbacks with value.
RunResolveTask(aValue, Resolved, aAsynchronous);
MaybeSettle(aValue, Resolved);
}
void
Promise::RejectInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aAsynchronous)
JS::Handle<JS::Value> aValue)
{
mResolvePending = true;
// If the synchronous flag is set, process our reject callbacks with
// value. Otherwise, the synchronous flag is unset, queue a task to process
// promise's reject callbacks with value.
RunResolveTask(aValue, Rejected, aAsynchronous);
MaybeSettle(aValue, Rejected);
}
void
Promise::RunResolveTask(JS::Handle<JS::Value> aValue,
PromiseState aState,
PromiseTaskSync aAsynchronous)
Promise::MaybeSettle(JS::Handle<JS::Value> aValue,
PromiseState aState)
{
// If the synchronous flag is unset, queue a task to process our
// accept callbacks with value.
if (aAsynchronous == AsyncTask) {
nsRefPtr<PromiseResolverTask> task =
new PromiseResolverTask(this, aValue, aState);
DispatchToMainOrWorkerThread(task);
return;
}
// Promise.all() or Promise.race() implementations will repeatedly call
// Resolve/RejectInternal rather than using the Maybe... forms. Stop SetState
// from asserting.
@ -1195,7 +1134,23 @@ Promise::RunResolveTask(JS::Handle<JS::Value> aValue,
}
}
RunTask();
EnqueueCallbackTasks();
}
void
Promise::EnqueueCallbackTasks()
{
nsTArray<nsRefPtr<PromiseCallback>> callbacks;
callbacks.SwapElements(mState == Resolved ? mResolveCallbacks
: mRejectCallbacks);
mResolveCallbacks.Clear();
mRejectCallbacks.Clear();
for (uint32_t i = 0; i < callbacks.Length(); ++i) {
nsRefPtr<PromiseCallbackTask> task =
new PromiseCallbackTask(this, callbacks[i], mResult);
DispatchToMicroTask(task);
}
}
void
@ -1295,8 +1250,7 @@ public:
// TODO Bug 975246 - nsRefPtr should support operator |nsRefPtr->*funcType|.
(workerPromise.get()->*mFunc)(aCx,
value,
Promise::PromiseTaskSync::SyncTask);
value);
// Release the Promise because it has been resolved/rejected for sure.
mPromiseWorkerProxy->CleanUp(aCx);

View File

@ -111,6 +111,9 @@ public:
// specializations in the .cpp for
// the T values we support.
// Called by DOM to let us execute our callbacks. May be called recursively.
static void PerformMicroTaskCheckpoint();
// WebIDL
nsIGlobalObject* GetParentObject() const
@ -165,9 +168,9 @@ protected:
virtual ~Promise();
// Queue an async task to current main or worker thread.
// Queue an async microtask to current main or worker thread.
static void
DispatchToMainOrWorkerThread(nsIRunnable* aRunnable);
DispatchToMicroTask(nsIRunnable* aRunnable);
// Do JS-wrapping after Promise creation.
void CreateWrapper(ErrorResult& aRv);
@ -194,11 +197,6 @@ private:
Rejected
};
enum PromiseTaskSync {
SyncTask,
AsyncTask
};
void SetState(PromiseState aState)
{
MOZ_ASSERT(mState == Pending);
@ -211,15 +209,14 @@ private:
mResult = aValue;
}
// This method processes promise's resolve/reject callbacks with promise's
// This method enqueues promise's resolve/reject callbacks with promise's
// result. It's executed when the resolver.resolve() or resolver.reject() is
// called or when the promise already has a result and new callbacks are
// appended by then(), catch() or done().
void RunTask();
void EnqueueCallbackTasks();
void RunResolveTask(JS::Handle<JS::Value> aValue,
Promise::PromiseState aState,
PromiseTaskSync aAsynchronous);
void MaybeSettle(JS::Handle<JS::Value> aValue,
Promise::PromiseState aState);
void AppendCallbacks(PromiseCallback* aResolveCallback,
PromiseCallback* aRejectCallback);
@ -236,19 +233,14 @@ private:
}
void MaybeResolveInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aSync = AsyncTask);
JS::Handle<JS::Value> aValue);
void MaybeRejectInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aSync = AsyncTask);
JS::Handle<JS::Value> aValue);
void ResolveInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aSync = AsyncTask);
JS::Handle<JS::Value> aValue);
void RejectInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aSync = AsyncTask);
JS::Handle<JS::Value> aValue);
template <typename T>
void MaybeSomething(T& aArgument, MaybeFunc aFunc) {
@ -313,7 +305,6 @@ private:
// have a fulfillment stack.
JS::Heap<JSObject*> mFullfillmentStack;
PromiseState mState;
bool mTaskPending;
bool mHadRejectCallback;
bool mResolvePending;

View File

@ -84,7 +84,7 @@ ResolvePromiseCallback::Call(JSContext* aCx,
return;
}
mPromise->ResolveInternal(aCx, value, Promise::SyncTask);
mPromise->ResolveInternal(aCx, value);
}
// RejectPromiseCallback
@ -143,7 +143,7 @@ RejectPromiseCallback::Call(JSContext* aCx,
}
mPromise->RejectInternal(aCx, value, Promise::SyncTask);
mPromise->RejectInternal(aCx, value);
}
// WrapperPromiseCallback
@ -279,7 +279,7 @@ WrapperPromiseCallback::Call(JSContext* aCx,
return;
}
mNextPromise->RejectInternal(aCx, typeError, Promise::SyncTask);
mNextPromise->RejectInternal(aCx, typeError);
return;
}
}

View File

@ -91,8 +91,7 @@ private:
// Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
typedef void (Promise::*RunCallbackFunc)(JSContext*,
JS::Handle<JS::Value>,
Promise::PromiseTaskSync);
JS::Handle<JS::Value>);
void RunCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,

View File

@ -116,20 +116,57 @@ function promiseGC() {
resolve(42);
}
function promiseAsync() {
var global = "foo";
var f = new Promise(function(r1, r2) {
is(global, "foo", "Global should be foo");
r1(42);
is(global, "foo", "Global should still be foo");
setTimeout(function() {
is(global, "bar", "Global should still be bar!");
runTest();
}, 0);
}).then(function() {
global = "bar";
function promiseAsync_TimeoutResolveThen() {
var handlerExecuted = false;
setTimeout(function() {
ok(handlerExecuted, "Handler should have been called before the timeout.");
// Allow other assertions to run so the test could fail before the next one.
setTimeout(runTest, 0);
}, 0);
Promise.resolve().then(function() {
handlerExecuted = true;
});
is(global, "foo", "Global should still be foo (2)");
ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
}
function promiseAsync_ResolveTimeoutThen() {
var handlerExecuted = false;
var promise = Promise.resolve();
setTimeout(function() {
ok(handlerExecuted, "Handler should have been called before the timeout.");
// Allow other assertions to run so the test could fail before the next one.
setTimeout(runTest, 0);
}, 0);
promise.then(function() {
handlerExecuted = true;
});
ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
}
function promiseAsync_ResolveThenTimeout() {
var handlerExecuted = false;
Promise.resolve().then(function() {
handlerExecuted = true;
});
setTimeout(function() {
ok(handlerExecuted, "Handler should have been called before the timeout.");
// Allow other assertions to run so the test could fail before the next one.
setTimeout(runTest, 0);
}, 0);
ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
}
function promiseDoubleThen() {
@ -715,7 +752,10 @@ function promiseWrapperAsyncResolution()
}
var tests = [ promiseResolve, promiseReject,
promiseException, promiseGC, promiseAsync,
promiseException, promiseGC,
promiseAsync_TimeoutResolveThen,
promiseAsync_ResolveTimeoutThen,
promiseAsync_ResolveThenTimeout,
promiseDoubleThen, promiseThenException,
promiseThenCatchThen, promiseRejectThenCatchThen,
promiseRejectThenCatchThen2,

View File

@ -0,0 +1,56 @@
/* -*- Mode: IDL; 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/.
*/
/*
* This file declares the AnonymousContent interface which is used to
* manipulate content that has been inserted into the document's canvasFrame
* anonymous container.
* See Document.insertAnonymousContent.
*
* This API never returns a reference to the actual inserted DOM node on
* purpose. This is to make sure the content cannot be randomly changed and the
* DOM cannot be traversed from the node, so that Gecko can remain in control of
* the inserted content.
*/
[ChromeOnly]
interface AnonymousContent {
/**
* Get the text content of an element inside this custom anonymous content.
*/
[Throws]
DOMString getTextContentForElement(DOMString elementId);
/**
* Set the text content of an element inside this custom anonymous content.
*/
[Throws]
void setTextContentForElement(DOMString elementId, DOMString text);
/**
* Get the value of an attribute of an element inside this custom anonymous
* content.
*/
[Throws]
DOMString? getAttributeForElement(DOMString elementId,
DOMString attributeName);
/**
* Set the value of an attribute of an element inside this custom anonymous
* content.
*/
[Throws]
void setAttributeForElement(DOMString elementId,
DOMString attributeName,
DOMString value);
/**
* Remove an attribute from an element inside this custom anonymous content.
*/
[Throws]
void removeAttributeForElement(DOMString elementId,
DOMString attributeName);
};

View File

@ -355,6 +355,30 @@ partial interface Document {
[ChromeOnly] readonly attribute boolean isSrcdocDocument;
};
/**
* Chrome document anonymous content management.
* This is a Chrome-only API that allows inserting fixed positioned anonymous
* content on top of the current page displayed in the document.
* The supplied content is cloned and inserted into the document's CanvasFrame.
* Note that this only works for HTML documents.
*/
partial interface Document {
/**
* Deep-clones the provided element and inserts it into the CanvasFrame.
* Returns an AnonymousContent instance that can be used to manipulate the
* inserted element.
*/
[ChromeOnly, NewObject, Throws]
AnonymousContent insertAnonymousContent(Element aElement);
/**
* Removes the element inserted into the CanvasFrame given an AnonymousContent
* instance.
*/
[ChromeOnly, Throws]
void removeAnonymousContent(AnonymousContent aContent);
};
Document implements XPathEvaluator;
Document implements GlobalEventHandlers;
Document implements TouchEventHandlers;

View File

@ -27,6 +27,7 @@ WEBIDL_FILES = [
'AnimationEvent.webidl',
'AnimationPlayer.webidl',
'AnimationTimeline.webidl',
'AnonymousContent.webidl',
'AppInfo.webidl',
'AppNotificationServiceOptions.webidl',
'Apps.webidl',

View File

@ -49,6 +49,7 @@
#include "mozilla/dom/MessageEvent.h"
#include "mozilla/dom/MessageEventBinding.h"
#include "mozilla/dom/MessagePortList.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/StructuredClone.h"
#include "mozilla/dom/WorkerBinding.h"
@ -4112,6 +4113,10 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
// Process a single runnable from the main queue.
MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false));
// Only perform the Promise microtask checkpoint on the outermost event
// loop. Don't run it, for example, during sync XHR or importScripts.
Promise::PerformMicroTaskCheckpoint();
if (NS_HasPendingEvents(mThread)) {
// Now *might* be a good time to GC. Let the JS engine make the decision.
if (workerCompartment) {

View File

@ -95,20 +95,81 @@ function promiseException() {
});
}
function promiseAsync() {
var global = "foo";
var f = new Promise(function(r1, r2) {
is(global, "foo", "Global should be foo");
r1(42);
is(global, "foo", "Global should still be foo");
setTimeout(function() {
is(global, "bar", "Global should still be bar!");
runTest();
}, 0);
}).then(function() {
global = "bar";
function promiseAsync_TimeoutResolveThen() {
var handlerExecuted = false;
setTimeout(function() {
ok(handlerExecuted, "Handler should have been called before the timeout.");
// Allow other assertions to run so the test could fail before the next one.
setTimeout(runTest, 0);
}, 0);
Promise.resolve().then(function() {
handlerExecuted = true;
});
is(global, "foo", "Global should still be foo (2)");
ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
}
function promiseAsync_ResolveTimeoutThen() {
var handlerExecuted = false;
var promise = Promise.resolve();
setTimeout(function() {
ok(handlerExecuted, "Handler should have been called before the timeout.");
// Allow other assertions to run so the test could fail before the next one.
setTimeout(runTest, 0);
}, 0);
promise.then(function() {
handlerExecuted = true;
});
ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
}
function promiseAsync_ResolveThenTimeout() {
var handlerExecuted = false;
Promise.resolve().then(function() {
handlerExecuted = true;
});
setTimeout(function() {
ok(handlerExecuted, "Handler should have been called before the timeout.");
// Allow other assertions to run so the test could fail before the next one.
setTimeout(runTest, 0);
}, 0);
ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
}
function promiseAsync_SyncHXRAndImportScripts()
{
var handlerExecuted = false;
Promise.resolve().then(function() {
handlerExecuted = true;
// Allow other assertions to run so the test could fail before the next one.
setTimeout(runTest, 0);
});
ok(!handlerExecuted, "Handlers are not called until the next microtask.");
var xhr = new XMLHttpRequest();
xhr.open("GET", "testXHR.txt", false);
xhr.send(null);
ok(!handlerExecuted, "Sync XHR should not trigger microtask execution.");
importScripts("relativeLoad_import.js");
ok(!handlerExecuted, "importScripts should not trigger microtask execution.");
}
function promiseDoubleThen() {
@ -683,11 +744,53 @@ function promiseResolveThenableCleanStack() {
},1000);
}
// Bug 1062323
function promiseWrapperAsyncResolution()
{
var p = new Promise(function(resolve, reject){
resolve();
});
var results = [];
var q = p.then(function () {
results.push("1-1");
}).then(function () {
results.push("1-2");
}).then(function () {
results.push("1-3");
});
var r = p.then(function () {
results.push("2-1");
}).then(function () {
results.push("2-2");
}).then(function () {
results.push("2-3");
});
Promise.all([q, r]).then(function() {
var match = results[0] == "1-1" &&
results[1] == "2-1" &&
results[2] == "1-2" &&
results[3] == "2-2" &&
results[4] == "1-3" &&
results[5] == "2-3";
ok(match, "Chained promises should resolve asynchronously.");
runTest();
}, function() {
ok(false, "promiseWrapperAsyncResolution: One of the promises failed.");
runTest();
});
}
var tests = [
promiseResolve,
promiseReject,
promiseException,
promiseAsync,
promiseAsync_TimeoutResolveThen,
promiseAsync_ResolveTimeoutThen,
promiseAsync_ResolveThenTimeout,
promiseAsync_SyncHXRAndImportScripts,
promiseDoubleThen,
promiseThenException,
promiseThenCatchThen,
@ -729,6 +832,8 @@ var tests = [
promiseResolvePromise,
promiseResolveThenableCleanStack,
promiseWrapperAsyncResolution,
];
function runTest() {

View File

@ -22,7 +22,7 @@
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/PromiseBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/TextDecoderBinding.h"
#include "mozilla/dom/TextEncoderBinding.h"
#include "mozilla/dom/DOMErrorBinding.h"
@ -1053,7 +1053,10 @@ nsXPConnect::AfterProcessNextEvent(nsIThreadInternal *aThread,
// Call cycle collector occasionally.
MOZ_ASSERT(NS_IsMainThread());
nsJSContext::MaybePokeCC();
nsDOMMutationObserver::HandleMutations();
nsContentUtils::PerformMainThreadMicroTaskCheckpoint();
Promise::PerformMicroTaskCheckpoint();
PopJSContextNoScriptContext();

View File

@ -21,6 +21,7 @@
#include "nsFrameManager.h"
#include "gfxPlatform.h"
#include "nsPrintfCString.h"
#include "mozilla/dom/AnonymousContent.h"
// for touchcaret
#include "nsContentList.h"
#include "nsContentCreatorFunctions.h"
@ -35,6 +36,7 @@
//#define DEBUG_CANVAS_FOCUS
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::layout;
using namespace mozilla::gfx;
@ -63,7 +65,7 @@ nsCanvasFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
ErrorResult er;
// We won't create touch caret element if preference is not enabled.
if (PresShell::TouchCaretPrefEnabled()) {
nsRefPtr<dom::NodeInfo> nodeInfo;
nsRefPtr<NodeInfo> nodeInfo;
// Create and append touch caret frame.
nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nullptr,
@ -72,7 +74,7 @@ nsCanvasFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
rv = NS_NewHTMLElement(getter_AddRefs(mTouchCaretElement), nodeInfo.forget(),
mozilla::dom::NOT_FROM_PARSER);
NOT_FROM_PARSER);
NS_ENSURE_SUCCESS(rv, rv);
aElements.AppendElement(mTouchCaretElement);
@ -103,6 +105,23 @@ nsCanvasFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
NS_ENSURE_SUCCESS(rv, rv);
}
// Create the custom content container.
mCustomContentContainer = doc->CreateHTMLElement(nsGkAtoms::div);
aElements.AppendElement(mCustomContentContainer);
// XXX add :moz-native-anonymous or will that be automatically set?
rv = mCustomContentContainer->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
NS_LITERAL_STRING("moz-custom-content-container"),
true);
NS_ENSURE_SUCCESS(rv, rv);
// Append all existing AnonymousContent nodes stored at document level if any.
int32_t anonymousContentCount = doc->GetAnonymousContents().Length();
for (int32_t i = 0; i < anonymousContentCount; ++i) {
nsCOMPtr<Element> node = doc->GetAnonymousContents()[i]->GetContentNode();
mCustomContentContainer->AppendChildTo(node->AsContent(), true);
}
return NS_OK;
}
@ -120,6 +139,8 @@ nsCanvasFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements, uint32
if (mSelectionCaretsEndElement) {
aElements.AppendElement(mSelectionCaretsEndElement);
}
aElements.AppendElement(mCustomContentContainer);
}
void
@ -134,6 +155,22 @@ nsCanvasFrame::DestroyFrom(nsIFrame* aDestructRoot)
nsContentUtils::DestroyAnonymousContent(&mTouchCaretElement);
nsContentUtils::DestroyAnonymousContent(&mSelectionCaretsStartElement);
nsContentUtils::DestroyAnonymousContent(&mSelectionCaretsEndElement);
// Elements inserted in the custom content container have the same lifetime as
// the document, so before destroying the container, make sure to keep a clone
// of each of them at document level so they can be re-appended on reframe.
if (mCustomContentContainer) {
nsCOMPtr<nsIDocument> doc = mContent->OwnerDoc();
ErrorResult rv;
for (int32_t i = doc->GetAnonymousContents().Length() - 1; i >= 0; --i) {
AnonymousContent* content = doc->GetAnonymousContents()[i];
nsCOMPtr<nsINode> clonedElement = content->GetContentNode()->CloneNode(true, rv);
content->SetContentNode(clonedElement->AsElement());
}
}
nsContentUtils::DestroyAnonymousContent(&mCustomContentContainer);
nsContainerFrame::DestroyFrom(aDestructRoot);
}

View File

@ -87,6 +87,11 @@ public:
return mSelectionCaretsEndElement;
}
mozilla::dom::Element* GetCustomContentContainer() const
{
return mCustomContentContainer;
}
/** SetHasFocus tells the CanvasFrame to draw with focus ring
* @param aHasFocus true to show focus ring, false to hide it
*/
@ -138,6 +143,7 @@ protected:
nsCOMPtr<mozilla::dom::Element> mTouchCaretElement;
nsCOMPtr<mozilla::dom::Element> mSelectionCaretsStartElement;
nsCOMPtr<mozilla::dom::Element> mSelectionCaretsEndElement;
nsCOMPtr<mozilla::dom::Element> mCustomContentContainer;
};
/**

View File

@ -6914,8 +6914,12 @@ nsIFrame::GetFrameFromDirection(nsDirection aDirection, bool aVisual,
frameTraversal->Prev();
traversedFrame = frameTraversal->CurrentItem();
if (!traversedFrame)
// Skip anonymous elements
if (!traversedFrame ||
traversedFrame->GetContent()->IsRootOfNativeAnonymousSubtree())
return NS_ERROR_FAILURE;
traversedFrame->IsSelectable(&selectable, nullptr);
} // while (!selectable)

View File

@ -2410,7 +2410,7 @@ public:
virtual nsresult PeekOffset(nsPeekOffsetStruct *aPos);
/**
* called to find the previous/next selectable leaf frame.
* called to find the previous/next non-anonymous selectable leaf frame.
* @param aDirection [in] the direction to move in (eDirPrevious or eDirNext)
* @param aVisual [in] whether bidi caret behavior is visual (true) or logical (false)
* @param aJumpLines [in] whether to allow jumping across line boundaries

View File

@ -390,3 +390,17 @@ div:-moz-native-anonymous.moz-selectioncaret-right.hidden {
margin: 0px;
visibility: hidden;
}
/* Custom content container in the CanvasFrame, fixed positioned on top of
everything else, not reacting to pointer events. */
div:-moz-native-anonymous.moz-custom-content-container {
pointer-events: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2147483648;
}

View File

@ -1190,6 +1190,8 @@ public abstract class GeckoApp
Class.forName("android.os.AsyncTask");
} catch (ClassNotFoundException e) {}
MemoryMonitor.getInstance().init(getApplicationContext());
// GeckoAppShell is tightly coupled to us, rather than
// the app context, because various parts of Fennec (e.g.,
// GeckoScreenOrientation) use GAS to access the Activity in
@ -1199,6 +1201,13 @@ public abstract class GeckoApp
GeckoAppShell.setContextGetter(this);
GeckoAppShell.setGeckoInterface(this);
Tabs.getInstance().attachToContext(this);
try {
Favicons.initializeWithContext(this);
} catch (Exception e) {
Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e);
}
// Did the OS locale change while we were backgrounded? If so,
// we need to die so that Gecko will re-init add-ons that touch
// the UI.
@ -1245,20 +1254,6 @@ public abstract class GeckoApp
}, 1000 * 5 /* 5 seconds */);
}
// Heavy load on the Gecko thread can slow down the time it takes for UI to appear on
// single-core devices. By minimizing the Gecko thread priority, we ensure that the UI
// appears quickly. The priority is reset to normal once thumbnails are loaded.
ThreadUtils.reduceGeckoPriority();
MemoryMonitor.getInstance().init(getApplicationContext());
Tabs.getInstance().attachToContext(this);
try {
Favicons.initializeWithContext(this);
} catch (Exception e) {
Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e);
}
Bundle stateBundle = getIntent().getBundleExtra(EXTRA_STATE_BUNDLE);
if (stateBundle != null) {
// Use the state bundle if it was given as an intent extra. This is
@ -1635,10 +1630,6 @@ public abstract class GeckoApp
} else if (NotificationHelper.HELPER_BROADCAST_ACTION.equals(action)) {
NotificationHelper.getInstance(getApplicationContext()).handleNotificationIntent(intent);
}
// Reset Gecko to normal priority. We may reduce the
// priority again later, e.g. for loading thumbnails.
ThreadUtils.resetGeckoPriority();
}
private String restoreSessionTabs(final boolean isExternalURL) throws SessionRestoreException {

View File

@ -51,7 +51,6 @@ public class GeckoThread extends Thread implements GeckoEventListener {
if (isCreated())
return false;
sGeckoThread = new GeckoThread(sArgs, sAction, sUri);
ThreadUtils.sGeckoThread = sGeckoThread;
return true;
}
@ -165,6 +164,7 @@ public class GeckoThread extends Thread implements GeckoEventListener {
@Override
public void run() {
Looper.prepare();
ThreadUtils.sGeckoThread = this;
ThreadUtils.sGeckoHandler = new Handler();
ThreadUtils.sGeckoQueue = Looper.myQueue();

View File

@ -98,6 +98,9 @@ public class TopSitesPanel extends HomeFragment {
// Max number of entries shown in the grid from the cursor.
private int mMaxGridEntries;
// Time in ms until the Gecko thread is reset to normal priority.
private static final long PRIORITY_RESET_TIMEOUT = 10000;
public static TopSitesPanel newInstance() {
return new TopSitesPanel();
}
@ -344,7 +347,7 @@ public class TopSitesPanel extends HomeFragment {
// appear, especially during startup (bug 897162). By minimizing the
// Gecko thread priority, we ensure that the UI appears quickly. The
// priority is reset to normal once thumbnails are loaded.
ThreadUtils.reduceGeckoPriority();
ThreadUtils.reduceGeckoPriority(PRIORITY_RESET_TIMEOUT);
}
/**

View File

@ -5,7 +5,10 @@
skip-if = android_version == "10"
[testAddonManager]
# disabled on x86 only; bug 936216
skip-if = processor == "x86"
#skip-if = processor == "x86"
# disabled across the board - bug 941624, bug 1063509, bug 1073374, bug 1087221,
# bug 1088023, bug 1088027, bug 1090206
skip-if = true
[testAddSearchEngine]
# disabled on Android 2.3; bug 979552
skip-if = android_version == "10"

View File

@ -14,8 +14,8 @@ public class testLinkContextMenu extends ContentContextMenuTest {
LINK_PAGE_URL=getAbsoluteUrl(StringHelper.ROBOCOP_BIG_LINK_URL);
BLANK_PAGE_URL=getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
loadAndPaint(LINK_PAGE_URL);
verifyPageTitle(LINK_PAGE_TITLE, LINK_PAGE_URL);
inputAndLoadUrl(LINK_PAGE_URL);
waitForText(LINK_PAGE_TITLE);
verifyContextMenuItems(linkMenuItems); // Verify context menu items are correct
openTabFromContextMenu(linkMenuItems[0],2); // Test the "Open in New Tab" option - expecting 2 tabs: the original and the new one

View File

@ -12,8 +12,8 @@ public class testMailToContextMenu extends ContentContextMenuTest {
blockForGeckoReady();
MAILTO_PAGE_URL=getAbsoluteUrl(StringHelper.ROBOCOP_BIG_MAILTO_URL);
loadAndPaint(MAILTO_PAGE_URL);
verifyPageTitle(MAILTO_PAGE_TITLE, MAILTO_PAGE_URL);
inputAndLoadUrl(MAILTO_PAGE_URL);
waitForText(MAILTO_PAGE_TITLE);
verifyContextMenuItems(mailtoMenuItems);
verifyCopyOption(mailtoMenuItems[0], "foo.bar@example.com"); // Test the "Copy Email Address" option

View File

@ -17,9 +17,6 @@ import android.util.Log;
public final class ThreadUtils {
private static final String LOGTAG = "ThreadUtils";
// Time in ms until the Gecko thread is reset to normal priority.
private static final long PRIORITY_RESET_TIMEOUT = 10000;
/**
* Controls the action taken when a method like
* {@link ThreadUtils#assertOnUiThread(AssertBehavior)} detects a problem.
@ -212,8 +209,10 @@ public final class ThreadUtils {
*
* Note that there are no guards in place to prevent multiple calls
* to this method from conflicting with each other.
*
* @param timeout Timeout in ms after which the priority will be reset
*/
public static void reduceGeckoPriority() {
public static void reduceGeckoPriority(long timeout) {
if (Runtime.getRuntime().availableProcessors() > 1) {
// Don't reduce priority for multicore devices. We use availableProcessors()
// for its fast performance. It may give false negatives (i.e. multicore
@ -223,7 +222,7 @@ public final class ThreadUtils {
if (!sIsGeckoPriorityReduced && sGeckoThread != null) {
sIsGeckoPriorityReduced = true;
sGeckoThread.setPriority(Thread.MIN_PRIORITY);
getUiHandler().postDelayed(sPriorityResetRunnable, PRIORITY_RESET_TIMEOUT);
getUiHandler().postDelayed(sPriorityResetRunnable, timeout);
}
}

View File

@ -104,7 +104,7 @@ MappableExtractFile::Create(const char *name, Zip *zip, Zip::Stream *stream)
return nullptr;
}
z_stream zStream = stream->GetZStream(buffer);
zxx_stream zStream = stream->GetZStream(buffer);
/* Decompress */
if (inflateInit2(&zStream, -MAX_WBITS) != Z_OK) {

View File

@ -180,7 +180,7 @@ private:
mozilla::UniquePtr<_MappableBuffer> buffer;
/* Zlib data */
z_stream zStream;
zxx_stream zStream;
};
/**

View File

@ -72,8 +72,7 @@ SeekableZStream::DecompressChunk(void *where, size_t chunk, size_t length)
DEBUG_LOG("DecompressChunk #%" PRIdSize " @%p (%" PRIdSize "/% " PRIdSize ")",
chunk, where, length, chunkLen);
z_stream zStream;
memset(&zStream, 0, sizeof(zStream));
zxx_stream zStream;
zStream.avail_in = (isLastChunk ? totalSize : uint32_t(offsetTable[chunk + 1]))
- uint32_t(offsetTable[chunk]);
zStream.next_in = const_cast<Bytef *>(buffer + uint32_t(offsetTable[chunk]));

View File

@ -10,7 +10,98 @@
#include <vector>
#include <zlib.h>
#include "Utils.h"
#include "mozilla/Assertions.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
/**
* Helper class wrapping z_stream to avoid malloc() calls during
* inflate. Do not use for deflate.
* inflateInit allocates two buffers:
* - one for its internal state, which is "approximately 10K bytes" according
* to inflate.h from zlib.
* - one for the compression window, which depends on the window size passed
* to inflateInit2, but is never greater than 32K (1 << MAX_WBITS).
* Those buffers are created at instantiation time instead of when calling
* inflateInit2. When inflateInit2 is called, it will call zxx_stream::Alloc
* to get both these buffers. zxx_stream::Alloc will choose one of the
* pre-allocated buffers depending on the requested size.
*/
class zxx_stream: public z_stream
{
public:
zxx_stream() {
memset(this, 0, sizeof(z_stream));
zalloc = Alloc;
zfree = Free;
opaque = this;
}
private:
static void *Alloc(void *data, uInt items, uInt size)
{
size_t buf_size = items * size;
zxx_stream *zStream = reinterpret_cast<zxx_stream *>(data);
if (items == 1 && buf_size <= zStream->stateBuf.size) {
return zStream->stateBuf.get();
} else if (buf_size == zStream->windowBuf.size) {
return zStream->windowBuf.get();
} else {
MOZ_CRASH("No ZStreamBuf for allocation");
}
}
static void Free(void *data, void *ptr)
{
zxx_stream *zStream = reinterpret_cast<zxx_stream *>(data);
if (zStream->stateBuf.Equals(ptr)) {
zStream->stateBuf.Release();
} else if (zStream->windowBuf.Equals(ptr)) {
zStream->windowBuf.Release();
} else {
MOZ_CRASH("Pointer doesn't match a ZStreamBuf");
}
}
/**
* Helper class for each buffer.
*/
template <size_t Size>
class ZStreamBuf
{
public:
ZStreamBuf() : buf(new char[Size]), inUse(false) { }
char *get()
{
if (!inUse) {
inUse = true;
return buf.get();
} else {
MOZ_CRASH("ZStreamBuf already in use");
}
}
void Release()
{
memset(buf.get(), 0, Size);
inUse = false;
}
bool Equals(const void *other) { return other == buf.get(); }
static const size_t size = Size;
private:
mozilla::UniquePtr<char[]> buf;
bool inUse;
};
ZStreamBuf<0x3000> stateBuf; // 0x3000 is an arbitrary size above 10K.
ZStreamBuf<1 << MAX_WBITS> windowBuf;
};
/**
* Forward declaration
@ -87,14 +178,13 @@ public:
Type GetType() { return type; }
/**
* Returns a z_stream for use with inflate functions using the given
* Returns a zxx_stream for use with inflate functions using the given
* buffer as inflate output. The caller is expected to allocate enough
* memory for the Stream uncompressed size.
*/
z_stream GetZStream(void *buf)
zxx_stream GetZStream(void *buf)
{
z_stream zStream;
memset(&zStream, 0, sizeof(zStream));
zxx_stream zStream;
zStream.avail_in = compressedSize;
zStream.next_in = reinterpret_cast<Bytef *>(
const_cast<void *>(compressedBuf));

View File

@ -618,39 +618,6 @@ File.prototype = {
);
},
/**
* Read a number of bytes from the file and into a buffer.
*
* @param {Typed array | C pointer} buffer This buffer will be
* modified by another thread. Using this buffer before the |read|
* operation has completed is a BAD IDEA.
* @param {JSON} options
*
* @return {promise}
* @resolves {number} The number of bytes effectively read.
* @rejects {OS.File.Error}
*/
readTo: function readTo(buffer, options = {}) {
// If |buffer| is a typed array and there is no |bytes| options, we
// need to extract the |byteLength| now, as it will be lost by
// communication.
// Options might be a nullish value, so better check for that before using
// the |in| operator.
if (isTypedArray(buffer) && !(options && "bytes" in options)) {
// Preserve reference to option |outExecutionDuration|, if it is passed.
options = clone(options, ["outExecutionDuration"]);
options.bytes = buffer.byteLength;
}
// Note: Type.void_t.out_ptr.toMsg ensures that
// - the buffer is effectively shared (not neutered) between both
// threads;
// - we take care of any |byteOffset|.
return Scheduler.post("File_prototype_readTo",
[this._fdmsg,
Type.void_t.out_ptr.toMsg(buffer),
options],
buffer/*Ensure that |buffer| is not gc-ed*/);
},
/**
* Write bytes from a buffer to this file.
*

View File

@ -52,43 +52,22 @@ AbstractFile.prototype = {
/**
* Read bytes from this file to a new buffer.
*
* @param {number=} bytes If unspecified, read all the remaining bytes from
* this file. If specified, read |bytes| bytes, or less if the file does notclone
* contain that many bytes.
* @param {number=} maybeBytes (deprecated, please use options.bytes)
* @param {JSON} options
* @return {Uint8Array} An array containing the bytes read.
*/
read: function read(bytes, options = {}) {
options = clone(options);
options.bytes = bytes == null ? this.stat().size : bytes;
let buffer = new Uint8Array(options.bytes);
let size = this.readTo(buffer, options);
if (size == options.bytes) {
return buffer;
read: function read(maybeBytes, options = {}) {
if (typeof maybeBytes === "object") {
// Caller has skipped `maybeBytes` and provided an options object.
options = clone(maybeBytes);
maybeBytes = null;
} else {
return buffer.subarray(0, size);
options = clone(options || {});
}
},
/**
* Read bytes from this file to an existing buffer.
*
* Note that, by default, this function may perform several I/O
* operations to ensure that the buffer is as full as possible.
*
* @param {Typed Array | C pointer} buffer The buffer in which to
* store the bytes. The buffer must be large enough to
* accomodate |bytes| bytes.
* @param {*=} options Optionally, an object that may contain the
* following fields:
* - {number} bytes The number of |bytes| to write from the buffer. If
* unspecified, this is |buffer.byteLength|. Note that |bytes| is required
* if |buffer| is a C pointer.
*
* @return {number} The number of bytes actually read, which may be
* less than |bytes| if the file did not contain that many bytes left.
*/
readTo: function readTo(buffer, options = {}) {
if(!("bytes" in options)) {
options.bytes = maybeBytes == null ? this.stat().size : maybeBytes;
}
let buffer = new Uint8Array(options.bytes);
let {ptr, bytes} = SharedAll.normalizeToPointer(buffer, options.bytes);
let pos = 0;
while (pos < bytes) {
@ -99,8 +78,11 @@ AbstractFile.prototype = {
pos += chunkSize;
ptr = SharedAll.offsetBy(ptr, chunkSize);
}
return pos;
if (pos == options.bytes) {
return buffer;
} else {
return buffer.subarray(0, pos);
}
},
/**

View File

@ -152,7 +152,6 @@ let test = maketest("Main", function main(test) {
yield test_stat();
yield test_debug();
yield test_info_features_detect();
yield test_read_write();
yield test_position();
yield test_iter();
yield test_exists();
@ -220,62 +219,6 @@ let test_info_features_detect = maketest("features_detect", function features_de
});
});
/**
* Test OS.File.prototype.{read, readTo, write}
*/
let test_read_write = maketest("read_write", function read_write(test) {
return Task.spawn(function() {
// Test readTo/write
let currentDir = yield OS.File.getCurrentDirectory();
let pathSource = OS.Path.join(currentDir, EXISTING_FILE);
let pathDest = OS.Path.join(OS.Constants.Path.tmpDir,
"osfile async test.tmp");
let fileSource = yield OS.File.open(pathSource);
test.info("Input file opened");
let fileDest = yield OS.File.open(pathDest,
{ truncate: true, read: true, write: true});
test.info("Output file opened");
let stat = yield fileSource.stat();
test.info("Input stat worked");
let size = stat.size;
let array = new Uint8Array(size);
try {
test.info("Now calling readTo");
let readLength = yield fileSource.readTo(array);
test.info("ReadTo worked");
test.is(readLength, size, "ReadTo got all bytes");
let writeLength = yield fileDest.write(array);
test.info("Write worked");
test.is(writeLength, size, "Write wrote all bytes");
// Test read
yield fileSource.setPosition(0);
let readAllResult = yield fileSource.read();
test.info("ReadAll worked");
test.is(readAllResult.length, size, "ReadAll read all bytes");
test.is(Array.prototype.join.call(readAllResult),
Array.prototype.join.call(array),
"ReadAll result is correct");
} finally {
// Close stuff
yield fileSource.close();
yield fileDest.close();
test.info("Files are closed");
}
stat = yield OS.File.stat(pathDest);
test.is(stat.size, size, "Both files have the same size");
yield reference_compare_files(pathSource, pathDest, test);
// Cleanup.
OS.File.remove(pathDest);
});
});
/**
* Test file.{getPosition, setPosition}
*/
@ -284,13 +227,8 @@ let test_position = maketest("position", function position(test) {
let file = yield OS.File.open(EXISTING_FILE);
try {
let stat = yield file.stat();
test.info("Obtained file length");
let view = new Uint8Array(stat.size);
yield file.readTo(view);
let view = yield file.read();
test.info("First batch of content read");
let CHUNK_SIZE = 178;// An arbitrary number of bytes to read from the file
let pos = yield file.getPosition();
test.info("Obtained position");
@ -299,8 +237,7 @@ let test_position = maketest("position", function position(test) {
test.info("Changed position");
test.is(pos, view.byteLength - CHUNK_SIZE, "setPosition returned the correct position");
let view2 = new Uint8Array(CHUNK_SIZE);
yield file.readTo(view2);
let view2 = yield file.read();
test.info("Read the end of the file");
for (let i = 0; i < CHUNK_SIZE; ++i) {
if (view2[i] != view[i + view.byteLength - CHUNK_SIZE]) {

View File

@ -27,7 +27,6 @@ self.onmessage = function onmessage_start(msg) {
test_open_non_existing_file();
test_flush_open_file();
test_copy_existing_file();
test_readall_writeall_file();
test_position();
test_move_file();
test_iter_dir();
@ -184,190 +183,6 @@ function compare_files(test, sourcePath, destPath, prefix)
info(test + ": Comparison complete");
}
function test_readall_writeall_file()
{
let src_file_name =
OS.Path.join("chrome", "toolkit", "components", "osfile", "tests", "mochi",
"worker_test_osfile_front.js");
let tmp_file_name =
OS.Path.join(OS.Constants.Path.tmpDir, "test_osfile_front.tmp");
info("Starting test_readall_writeall_file");
// read, ArrayBuffer
let source = OS.File.open(src_file_name);
let dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
let size = source.stat().size;
let buf = new Uint8Array(size);
let readResult = source.readTo(buf);
is(readResult, size, "test_readall_writeall_file: read the right number of bytes");
dest.write(buf);
info("test_readall_writeall_file: copy complete (manual allocation)");
source.close();
dest.close();
compare_files("test_readall_writeall_file (manual allocation)", src_file_name, tmp_file_name);
OS.File.remove(tmp_file_name);
// read, C buffer
source = OS.File.open(src_file_name);
dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
buf = new ArrayBuffer(size);
let ptr = OS.Shared.Type.voidptr_t.implementation(buf);
readResult = source.readTo(ptr, {bytes: size});
is(readResult, size, "test_readall_writeall_file: read the right number of bytes (C buffer)");
dest.write(ptr, {bytes: size});
info("test_readall_writeall_file: copy complete (C buffer)");
source.close();
dest.close();
compare_files("test_readall_writeall_file (C buffer)", src_file_name, tmp_file_name);
OS.File.remove(tmp_file_name);
// read/write, C buffer, missing |bytes| option
source = OS.File.open(src_file_name);
dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
let exn = should_throw(function() { source.readTo(ptr); });
ok(exn != null && exn instanceof TypeError, "test_readall_writeall_file: read with C pointer and without bytes fails with the correct error");
exn = should_throw(function() { dest.write(ptr); });
ok(exn != null && exn instanceof TypeError, "test_readall_writeall_file: write with C pointer and without bytes fails with the correct error");
source.close();
dest.close();
// readTo, ArrayBuffer + offset
let OFFSET = 12;
let LEFT = size - OFFSET;
buf = new ArrayBuffer(size);
let offset_view = new Uint8Array(buf, OFFSET);
source = OS.File.open(src_file_name);
dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
readResult = source.readTo(offset_view);
is(readResult, LEFT, "test_readall_writeall_file: read the right number of bytes (with offset)");
dest.write(offset_view);
is(dest.stat().size, LEFT, "test_readall_writeall_file: wrote the right number of bytes (with offset)");
info("test_readall_writeall_file: copy complete (with offset)");
source.close();
dest.close();
compare_files("test_readall_writeall_file (with offset)", src_file_name, tmp_file_name, LEFT);
OS.File.remove(tmp_file_name);
// read
buf = new Uint8Array(size);
source = OS.File.open(src_file_name);
dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
readResult = source.read();
is(readResult.length, size, "test_readall_writeall_file: read the right number of bytes (auto allocation)");
dest.write(readResult);
info("test_readall_writeall_file: copy complete (auto allocation)");
source.close();
dest.close();
compare_files("test_readall_writeall_file (auto allocation)", src_file_name, tmp_file_name);
OS.File.remove(tmp_file_name);
// File.readAll
readResult = OS.File.read(src_file_name);
is(readResult.length, size, "test_readall_writeall_file: read the right number of bytes (OS.File.readAll)");
// File.writeAtomic on top of nothing
OS.File.writeAtomic(tmp_file_name, readResult,
{tmpPath: tmp_file_name + ".tmp"});
try {
let stat = OS.File.stat(tmp_file_name);
info("readAll + writeAtomic created a file");
is(stat.size, size, "readAll + writeAtomic created a file of the right size");
} catch (x) {
ok(false, "readAll + writeAtomic somehow failed");
if(x.becauseNoSuchFile) {
ok(false, "readAll + writeAtomic did not create file");
}
}
compare_files("test_readall_writeall_file (OS.File.readAll + writeAtomic)",
src_file_name, tmp_file_name);
exn = null;
try {
let stat = OS.File.stat(tmp_file_name + ".tmp");
} catch (x) {
exn = x;
}
ok(!!exn, "readAll + writeAtomic cleaned up after itself");
// File.writeAtomic on top of existing file
// Remove content and set arbitrary size, to avoid potential false negatives
dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
dest.setPosition(1234);
dest.close();
OS.File.writeAtomic(tmp_file_name, readResult,
{tmpPath: tmp_file_name + ".tmp"});
compare_files("test_readall_writeall_file (OS.File.readAll + writeAtomic 2)",
src_file_name, tmp_file_name);
// File.writeAtomic on top of existing file but without overwritten the file
exn = null;
try {
let view = new Uint8Array(readResult.buffer, 10, 200);
OS.File.writeAtomic(tmp_file_name, view,
{ tmpPath: tmp_file_name + ".tmp", noOverwrite: true});
} catch (x) {
exn = x;
}
ok(exn && exn instanceof OS.File.Error && exn.becauseExists, "writeAtomic fails if file already exists with noOverwrite option");
// Check file was not overwritten.
compare_files("test_readall_writeall_file (OS.File.readAll + writeAtomic check file was not overwritten)",
src_file_name, tmp_file_name);
// Ensure that File.writeAtomic fails if no temporary file name is provided
// (FIXME: Remove this test as part of bug 793660)
exn = null;
try {
OS.File.writeAtomic(tmp_file_name, readResult.buffer,
{bytes: readResult.length});
} catch (x) {
exn = x;
}
ok(!!exn && exn instanceof TypeError, "writeAtomic fails if tmpPath is not provided");
// Check that writeAtomic fails when destination path is undefined
exn = null;
try {
let path = undefined;
let options = {tmpPath: tmp_file_name};
OS.File.writeAtomic(path, readResult.buffer, options);
} catch (x) {
exn = x;
}
ok(!!exn && exn instanceof TypeError, "writeAtomic fails if path is undefined");
// Check that writeAtomic fails when destination path is an empty string
exn = null;
try {
let path = "";
let options = {tmpPath: tmp_file_name};
OS.File.writeAtomic(path, readResult.buffer, options);
} catch (x) {
exn = x;
}
ok(!!exn && exn instanceof TypeError, "writeAtomic fails if path is an empty string");
// Cleanup.
OS.File.remove(tmp_file_name);
}
/**
* Test that copying a file using |copy| works.
*/

View File

@ -9,7 +9,7 @@ function run_test() {
}
/**
* Test to ensure that {bytes:} in options to |readTo| and |write| are correctly
* Test to ensure that {bytes:} in options to |write| is correctly
* preserved.
*/
add_task(function* test_bytes() {
@ -23,12 +23,7 @@ add_task(function* test_bytes() {
yield file.write(new Uint8Array(2048), {bytes: 1024});
do_check_eq((yield file.stat()).size, 1024);
// 2. Test same for |readTo|.
yield file.setPosition(0, OS.File.POS_START);
let read = yield file.readTo(new Uint8Array(1024), {bytes: 512});
do_check_eq(read, 512);
// 3. Test that passing nullish values for |options| still works.
// 2. Test that passing nullish values for |options| still works.
yield file.setPosition(0, OS.File.POS_END);
yield file.write(new Uint8Array(1024), null);
yield file.write(new Uint8Array(1024), undefined);

View File

@ -116,7 +116,15 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
* @return {Array}
*/
get docShells() {
let docShellsEnum = this.tabActor.originalDocShell.getDocShellEnumerator(
let originalDocShell;
if (this.tabActor.isRootActor) {
originalDocShell = this.tabActor.docShell;
} else {
originalDocShell = this.tabActor.originalDocShell;
}
let docShellsEnum = originalDocShell.getDocShellEnumerator(
Ci.nsIDocShellTreeItem.typeAll,
Ci.nsIDocShell.ENUMERATE_FORWARDS
);

View File

@ -955,6 +955,12 @@ CycleCollectedJSRuntime::SetPendingException(nsIException* aException)
mPendingException = aException;
}
nsTArray<nsRefPtr<nsIRunnable>>&
CycleCollectedJSRuntime::GetPromiseMicroTaskQueue()
{
return mPromiseMicroTaskQueue;
}
nsCycleCollectionParticipant*
CycleCollectedJSRuntime::GCThingParticipant()
{

View File

@ -18,6 +18,7 @@
class nsCycleCollectionNoteRootCallback;
class nsIException;
class nsIRunnable;
namespace js {
struct Class;
@ -257,6 +258,8 @@ public:
already_AddRefed<nsIException> GetPendingException() const;
void SetPendingException(nsIException* aException);
nsTArray<nsRefPtr<nsIRunnable>>& GetPromiseMicroTaskQueue();
nsCycleCollectionParticipant* GCThingParticipant();
nsCycleCollectionParticipant* ZoneParticipant();
@ -306,6 +309,8 @@ private:
nsCOMPtr<nsIException> mPendingException;
nsTArray<nsRefPtr<nsIRunnable>> mPromiseMicroTaskQueue;
OOMState mOutOfMemoryState;
OOMState mLargeAllocationFailureState;
};