blob: e2cdaeec3bd3f6498c36ff3f64aaedc21eedd221 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of $LIBRARYNAME;
typedef void RemoveFrameRequestMapping(int id);
/**
* The task object representing animation-frame requests.
*
* For historical reasons, [Window.requestAnimationFrame] returns an integer
* to users. However, zone tasks must be unique objects, and an integer can
* therefore not be used as task object. The [Window] class thus keeps a mapping
* from the integer ID to the corresponding task object. All zone related
* operations work on this task object, whereas users of
* [Window.requestAnimationFrame] only see the integer ID.
*
* Since this mapping takes up space, it must be removed when the
* animation-frame task has triggered. The default implementation does this
* automatically, but intercepting implementations of `requestAnimationFrame`
* must make sure to call the [AnimationFrameTask.removeMapping]
* function that is provided in the task specification.
*
* *Experimental*. This class may disappear without notice.
*/
abstract class AnimationFrameTask {
/** The ID that is returned to users. */
int get id;
/** The zone in which the task will run. */
Zone get zone;
/**
* Cancels the animation-frame request.
*
* A call to [Window.cancelAnimationFrame] with an `id` argument equal to [id]
* forwards the request to this function.
*
* Zones that intercept animation-frame requests implement this method so
* that they can react to cancelation requests.
*/
void cancel(Window window);
/**
* Maps animation-frame request IDs to their task objects.
*/
static final Map<int, _AnimationFrameTask> _tasks = {};
/**
* Removes the mapping from [id] to [AnimationFrameTask].
*
* This function must be invoked by user-implemented animation-frame
* tasks, before running [callback].
*
* See [AnimationFrameTask].
*/
static void removeMapping(int id) {
_tasks.remove(id);
}
}
class _AnimationFrameTask implements AnimationFrameTask {
final int id;
final Zone zone;
final FrameRequestCallback _callback;
_AnimationFrameTask(this.id, this.zone, this._callback);
void cancel(Window window) {
window._cancelAnimationFrame(this.id);
}
}
/**
* The task specification for an animation-frame request.
*
* *Experimental*. This class may disappear without notice.
*/
class AnimationFrameRequestSpecification implements TaskSpecification {
/**
* The window on which [Window.requestAnimationFrame] was invoked.
*/
final Window window;
/**
* The callback that is executed when the animation-frame is ready.
*
* Note that the callback hasn't been registered in any zone when the `create`
* function (passed to [Zone.createTask]) is invoked.
*/
final FrameRequestCallback callback;
AnimationFrameRequestSpecification(this.window, this.callback);
String get name => "dart.html.request-animation-frame";
bool get isOneShot => true;
}
@DocsEditable()
$if DART2JS
$(ANNOTATIONS)@Native("Window,DOMWindow")
$(CLASS_MODIFIERS)class $CLASSNAME$EXTENDS$IMPLEMENTS {
$else
$(ANNOTATIONS)$(NATIVESPEC)$(CLASS_MODIFIERS)class $CLASSNAME$EXTENDS$IMPLEMENTS {
$endif
/**
* Returns a Future that completes just before the window is about to
* repaint so the user can draw an animation frame.
*
* If you need to later cancel this animation, use [requestAnimationFrame]
* instead.
*
* The [Future] completes to a timestamp that represents a floating
* point value of the number of milliseconds that have elapsed since the page
* started to load (which is also the timestamp at this call to
* animationFrame).
*
* Note: The code that runs when the future completes should call
* [animationFrame] again for the animation to continue.
*/
Future<num> get animationFrame {
var completer = new Completer<num>.sync();
requestAnimationFrame(completer.complete);
return completer.future;
}
$if DART2JS
/**
* The newest document in this window.
*
* ## Other resources
*
* * [Loading web
* pages](https://html.spec.whatwg.org/multipage/browsers.html)
* from WHATWG.
*/
Document get document => JS('Document', '#.document', this);
WindowBase _open2(url, name) =>
JS('Window|Null', '#.open(#,#)', this, url, name);
WindowBase _open3(url, name, options) =>
JS('Window|Null', '#.open(#,#,#)', this, url, name, options);
/**
* Opens a new window.
*
* ## Other resources
*
* * [Window.open](https://developer.mozilla.org/en-US/docs/Web/API/Window.open)
* from MDN.
* * [Window open](http://docs.webplatform.org/wiki/dom/methods/open)
* from WebPlatform.org.
*/
WindowBase open(String url, String name, [String options]) {
if (options == null) {
return _DOMWindowCrossFrame._createSafe(_open2(url, name));
} else {
return _DOMWindowCrossFrame._createSafe(_open3(url, name, options));
}
}
// API level getter and setter for Location.
// TODO: The cross domain safe wrapper can be inserted here.
/**
* The current location of this window.
*
* Location currentLocation = window.location;
* print(currentLocation.href); // 'http://www.example.com:80/'
*/
Location get location => _location;
// TODO: consider forcing users to do: window.location.assign('string').
/**
* Sets the window's location, which causes the browser to navigate to the new
* location. [value] may be a Location object or a String.
*/
set location(value) {
_location = value;
}
// Native getter and setter to access raw Location object.
dynamic get _location => JS('Location|Null', '#.location', this);
set _location(value) {
JS('void', '#.location = #', this, value);
}
$endif
/**
* Called to draw an animation frame and then request the window to repaint
* after [callback] has finished (creating the animation).
*
* Use this method only if you need to later call [cancelAnimationFrame]. If
* not, the preferred Dart idiom is to set animation frames by calling
* [animationFrame], which returns a Future.
*
* Returns a non-zero valued integer to represent the request id for this
* request. This value only needs to be saved if you intend to call
* [cancelAnimationFrame] so you can specify the particular animation to
* cancel.
*
* Note: The supplied [callback] needs to call [requestAnimationFrame] again
* for the animation to continue.
*/
@DomName('Window.requestAnimationFrame')
int requestAnimationFrame(FrameRequestCallback callback) {
$if DART2JS
_ensureRequestAnimationFrame();
$endif
if (identical(Zone.current, Zone.ROOT)) {
return _requestAnimationFrame(callback);
}
var spec = new AnimationFrameRequestSpecification(this, callback);
var task = Zone.current.createTask/*<AnimationFrameTask>*/(
_createAnimationFrameTask, spec);
AnimationFrameTask._tasks[task.id] = task;
return task.id;
}
static _AnimationFrameTask _createAnimationFrameTask(
AnimationFrameRequestSpecification spec, Zone zone) {
var task;
var id = spec.window._requestAnimationFrame((num time) {
AnimationFrameTask.removeMapping(task.id);
zone.runTask(_runAnimationFrame, task, time);
});
var callback = zone.registerUnaryCallback(spec.callback);
task = new _AnimationFrameTask(id, zone, callback);
return task;
}
static void _runAnimationFrame(_AnimationFrameTask task, num time) {
task._callback(time);
}
/**
* Cancels an animation frame request.
*
* ## Other resources
*
* * [Window.cancelAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/Window.cancelAnimationFrame)
* from MDN.
*/
@DomName('Window.cancelAnimationFrame')
void cancelAnimationFrame(int id) {
$if DART2JS
_ensureRequestAnimationFrame();
$endif
var task = AnimationFrameTask._tasks.remove(id);
if (task == null) {
// Assume that the animation frame request wasn't intercepted by a zone.
_cancelAnimationFrame(id);
return;
}
task.cancel(this);
}
$if DART2JS
@JSName('requestAnimationFrame')
int _requestAnimationFrame(FrameRequestCallback callback) native;
@JSName('cancelAnimationFrame')
void _cancelAnimationFrame(int id) native;
_ensureRequestAnimationFrame() {
if (JS('bool',
'!!(#.requestAnimationFrame && #.cancelAnimationFrame)', this, this))
return;
JS('void',
r"""
(function($this) {
var vendors = ['ms', 'moz', 'webkit', 'o'];
for (var i = 0; i < vendors.length && !$this.requestAnimationFrame; ++i) {
$this.requestAnimationFrame = $this[vendors[i] + 'RequestAnimationFrame'];
$this.cancelAnimationFrame =
$this[vendors[i]+'CancelAnimationFrame'] ||
$this[vendors[i]+'CancelRequestAnimationFrame'];
}
if ($this.requestAnimationFrame && $this.cancelAnimationFrame) return;
$this.requestAnimationFrame = function(callback) {
return window.setTimeout(function() {
callback(Date.now());
}, 16 /* 16ms ~= 60fps */);
};
$this.cancelAnimationFrame = function(id) { clearTimeout(id); }
})(#)""",
this);
}
/**
* Gets an instance of the Indexed DB factory to being using Indexed DB.
*
* Use [indexed_db.IdbFactory.supported] to check if Indexed DB is supported on the
* current platform.
*/
@SupportedBrowser(SupportedBrowser.CHROME, '23.0')
@SupportedBrowser(SupportedBrowser.FIREFOX, '15.0')
@SupportedBrowser(SupportedBrowser.IE, '10.0')
@Experimental()
IdbFactory get indexedDB =>
JS('IdbFactory|Null', // If not supported, returns null.
'#.indexedDB || #.webkitIndexedDB || #.mozIndexedDB',
this, this, this);
/// The debugging console for this window.
@DomName('Window.console')
Console get console => Console._safeConsole;
$endif
/**
* Access a sandboxed file system of the specified `size`. If `persistent` is
* true, the application will request permission from the user to create
* lasting storage. This storage cannot be freed without the user's
* permission. Returns a [Future] whose value stores a reference to the
* sandboxed file system for use. Because the file system is sandboxed,
* applications cannot access file systems created in other web pages.
*/
Future<FileSystem> requestFileSystem(int size, {bool persistent: false}) {
return _requestFileSystem(persistent? 1 : 0, size);
}
/**
* convertPointFromNodeToPage and convertPointFromPageToNode are removed.
* see http://dev.w3.org/csswg/cssom-view/#geometry
*/
static bool get supportsPointConversions => DomPoint.supported;
$!MEMBERS
/**
* Static factory designed to expose `beforeunload` events to event
* handlers that are not necessarily instances of [Window].
*
* See [EventStreamProvider] for usage information.
*/
@DomName('Window.beforeunloadEvent')
static const EventStreamProvider<BeforeUnloadEvent> beforeUnloadEvent =
const _BeforeUnloadEventStreamProvider('beforeunload');
/// Stream of `beforeunload` events handled by this [Window].
@DomName('Window.onbeforeunload')
Stream<Event> get onBeforeUnload => beforeUnloadEvent.forTarget(this);
/**
* Moves this window to a specific position.
*
* x and y can be negative.
*
* ## Other resources
*
* * [Window.moveTo](https://developer.mozilla.org/en-US/docs/Web/API/Window.moveTo)
* from MDN.
* * [Window.moveTo](http://dev.w3.org/csswg/cssom-view/#dom-window-moveto)
* from W3C.
*/
void moveTo(Point p) {
_moveTo(p.x, p.y);
}
$if DART2JS
@DomName('Window.pageXOffset')
@DocsEditable()
int get pageXOffset => JS('num', '#.pageXOffset', this).round();
@DomName('Window.pageYOffset')
@DocsEditable()
int get pageYOffset => JS('num', '#.pageYOffset', this).round();
/**
* The distance this window has been scrolled horizontally.
*
* ## Other resources
*
* * [The Screen interface
* specification](http://www.w3.org/TR/cssom-view/#screen) from W3C.
* * [scrollX](https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollX)
* from MDN.
*/
@DomName('Window.scrollX')
@DocsEditable()
int get scrollX => JS('bool', '("scrollX" in #)', this) ?
JS('num', '#.scrollX', this).round() :
document.documentElement.scrollLeft;
/**
* The distance this window has been scrolled vertically.
*
* ## Other resources
*
* * [The Screen interface
* specification](http://www.w3.org/TR/cssom-view/#screen) from W3C.
* * [scrollY](https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY)
* from MDN.
*/
@DomName('Window.scrollY')
@DocsEditable()
int get scrollY => JS('bool', '("scrollY" in #)', this) ?
JS('num', '#.scrollY', this).round() :
document.documentElement.scrollTop;
$else
@DomName('Window.pageXOffset')
@DocsEditable()
int get pageXOffset => _blink.BlinkWindow.instance.pageXOffset_Getter_(this).round();
@DomName('Window.pageYOffset')
@DocsEditable()
int get pageYOffset => _blink.BlinkWindow.instance.pageYOffset_Getter_(this).round();
@DomName('Window.scrollX')
@DocsEditable()
int get scrollX => _blink.BlinkWindow.instance.scrollX_Getter_(this).round();
@DomName('Window.scrollY')
@DocsEditable()
int get scrollY => _blink.BlinkWindow.instance.scrollY_Getter_(this).round();
$endif
}
$if DART2JS
class _BeforeUnloadEvent extends _WrappedEvent implements BeforeUnloadEvent {
String _returnValue;
_BeforeUnloadEvent(Event base): super(base);
String get returnValue => _returnValue;
set returnValue(String value) {
_returnValue = value;
// FF and IE use the value as the return value, Chrome will return this from
// the event callback function.
if (JS('bool', '("returnValue" in #)', wrapped)) {
JS('void', '#.returnValue = #', wrapped, value);
}
}
}
$endif
class _BeforeUnloadEventStreamProvider implements
EventStreamProvider<BeforeUnloadEvent> {
final String _eventType;
const _BeforeUnloadEventStreamProvider(this._eventType);
Stream<BeforeUnloadEvent> forTarget(EventTarget e, {bool useCapture: false}) {
$if DART2JS
// Specify the generic type for EventStream only in dart2js to avoid
// checked mode errors in dartium.
var stream = new _EventStream<BeforeUnloadEvent>(e, _eventType, useCapture);
var controller = new StreamController<BeforeUnloadEvent>(sync: true);
stream.listen((event) {
var wrapped = new _BeforeUnloadEvent(event);
controller.add(wrapped);
});
return controller.stream;
$else
var stream = new _EventStream(e, _eventType, useCapture);
return stream;
$endif
}
String getEventType(EventTarget target) {
return _eventType;
}
ElementStream<BeforeUnloadEvent> forElement(Element e, {bool useCapture: false}) {
$if DART2JS
// Specify the generic type for _ElementEventStreamImpl only in dart2js to
// avoid checked mode errors in dartium.
return new _ElementEventStreamImpl<BeforeUnloadEvent>(e, _eventType, useCapture);
$else
return new _ElementEventStreamImpl(e, _eventType, useCapture);
$endif
}
ElementStream<BeforeUnloadEvent> _forElementList(ElementList e,
{bool useCapture: false}) {
$if DART2JS
// Specify the generic type for _ElementEventStreamImpl only in dart2js to
// avoid checked mode errors in dartium.
return new _ElementListEventStreamImpl<BeforeUnloadEvent>(e, _eventType, useCapture);
$else
return new _ElementListEventStreamImpl(e, _eventType, useCapture);
$endif
}
}