Release 5.2
diff --git a/src/js/page_context.js b/src/js/page_context.js
new file mode 100644
index 0000000..ef35563
--- /dev/null
+++ b/src/js/page_context.js
@@ -0,0 +1,300 @@
+var __screenCapturePageContext__ = {
+ clone: function(object) {
+ function StubObj() { }
+ StubObj.prototype = object;
+ var newObj = new StubObj();
+ newObj.getInternalObject = function() {
+ return this.__proto__;
+ }
+ newObj.toString = function() {
+ try {
+ return this.__proto__.toString();
+ } catch (e) {
+ return 'object Object';
+ }
+ }
+ return newObj;
+ },
+
+ bind: function(newThis, func) {
+ var args = [];
+ for(var i = 2;i < arguments.length; i++) {
+ args.push(arguments[i]);
+ }
+ return function() {
+ return func.apply(newThis, args);
+ }
+ },
+
+ bodyWrapperDelegate_: null,
+ currentHookStatus_: false,
+
+ scrollValueHooker: function(oldValue, newValue, reason) {
+ // When we hook the value of scrollLeft/Top of body, it always returns 0.
+ return 0;
+ },
+
+ toggleBodyScrollValueHookStatus: function() {
+ this.currentHookStatus_ = !this.currentHookStatus_;
+ if (this.currentHookStatus_) {
+ var This = this;
+ try {
+ document.__defineGetter__('body', function() {
+ return This.bodyWrapperDelegate_.getWrapper();
+ });
+ } catch (e) {
+ window.console.log('error' + e);
+ }
+ this.bodyWrapperDelegate_.watch('scrollLeft', this.scrollValueHooker);
+ this.bodyWrapperDelegate_.watch('scrollTop', this.scrollValueHooker);
+ } else {
+ this.bodyWrapperDelegate_.unwatch('scrollLeft', this.scrollValueHooker);
+ this.bodyWrapperDelegate_.unwatch('scrollTop', this.scrollValueHooker);
+ var This = this;
+ try {
+ document.__defineGetter__('body', function() {
+ return This.bodyWrapperDelegate_.getWrapper().getInternalObject();
+ });
+ } catch (e) {
+ window.console.log('error' + e);
+ }
+ }
+ },
+
+ checkHookStatus: function() {
+ var needHookScrollValue = document.documentElement.getAttributeNode(
+ '__screen_capture_need_hook_scroll_value__');
+ needHookScrollValue =
+ !!(needHookScrollValue && needHookScrollValue.nodeValue == 'true');
+ if (this.currentHookStatus_ != needHookScrollValue)
+ this.toggleBodyScrollValueHookStatus();
+ },
+
+ init: function() {
+ if (!this.bodyWrapperDelegate_) {
+ this.bodyWrapperDelegate_ =
+ new __screenCapturePageContext__.ObjectWrapDelegate(
+ document.body, '^(DOCUMENT_[A-Z_]+|[A-Z_]+_NODE)$');
+ document.documentElement.addEventListener(
+ '__screen_capture_check_hook_status_event__',
+ __screenCapturePageContext__.bind(this, this.checkHookStatus));
+ }
+ }
+};
+
+// ObjectWrapDelegate class will create a empty object(wrapper), map its
+// prototype to the 'originalObject', then search all non-function properties
+// (except those properties which match the propertyNameFilter) of the
+// 'orginalObject' and set corresponding getter/setter to the wrapper.
+// Then you can manipulate the wrapper as 'originalObject' because the wrapper
+// use the corresponding getter/setter to access the corresponding properties in
+// 'originalObject' and the all function calls can be call through the prototype
+// inherit.
+// After createing the wrapper object, you can use watch method to monitor any
+// property which you want to know when it has been read(get) or change(set).
+// Please see the detail comment on method watch.
+// Remember the ObjectWrapDelegate returns the wrapDelegateObject instead of
+// really wrapper object. You have to use ObjectWrapDelegate.getWrapper to get
+// real wrapper object.
+// parameter @originalObject, object which you want to wrap
+// parameter @propertyNameFilter, string, regular expression pattern string for
+// those properties you don't put in the wrap object.
+__screenCapturePageContext__.ObjectWrapDelegate = function(
+ originalObject, propertyNameFilter) {
+ this.window_ = window;
+
+ // The wrapper is the object we use to wrap the 'originalObject'.
+ this.wrapper_ = __screenCapturePageContext__.clone(originalObject);
+
+ // This array saves all properties we set our getter/setter for them.
+ this.properties_ = [];
+
+ // This object to save all watch handlers. Each watch handler is bind to one
+ // certain property which is in properties_.
+ this.watcherTable_ = {};
+
+ // Check the propertyNameFilter parameter.
+ if (typeof propertyNameFilter == 'undefined') {
+ propertyNameFilter = '';
+ } else if (typeof propertyNameFilter != 'string') {
+ try {
+ propertyNameFilter = propertyNameFilter.toString();
+ } catch (e) {
+ propertyNameFilter = '';
+ }
+ }
+ if (propertyNameFilter.length) {
+ this.propertyNameFilter_ = new RegExp('');
+ this.propertyNameFilter_.compile(propertyNameFilter);
+ } else {
+ this.propertyNameFilter_ = null;
+ }
+
+ // For closure to access the private data of class.
+ var This = this;
+
+ // Set the getter object.
+ function setGetterAndSetter(wrapper, propertyName) {
+ wrapper.__defineGetter__(propertyName, function() {
+ var internalObj = this.getInternalObject();
+ var originalReturnValue = internalObj[propertyName];
+ var returnValue = originalReturnValue;
+
+ // See whether this property has been watched.
+ var watchers = This.watcherTable_[propertyName];
+ if (watchers) {
+ // copy the watcher to a cache in case someone call unwatch inside the
+ // watchHandler.
+ var watchersCache = watchers.concat();
+ for (var i = 0, l = watchersCache.length; i < l; ++i) {
+ var watcher = watchersCache[i];
+ if (!watcher) {
+ window.console.log('wrapper\'s watch for ' + propertyName +
+ ' is unavailable!');
+ continue; // should never happend
+ }
+ originalReturnValue = returnValue;
+ try {
+ returnValue = watcher(returnValue, returnValue, 'get');
+ } catch (e) {
+ returnValue = originalReturnValue;
+ }
+ }
+ }
+ return returnValue;
+ });
+
+ // Set the setter object.
+ wrapper.__defineSetter__(propertyName, function(value) {
+ var internalObj = this.getInternalObject();
+ var originalValue = value;
+ var userValue = originalValue;
+ var oldValue;
+ try {
+ oldValue = internalObj[propertyName];
+ } catch (e) {
+ oldValue = null;
+ }
+
+ // See whether this property has been watched.
+ var watchers = This.watcherTable_[propertyName];
+ if (watchers) {
+ // copy the watcher to a cache in case someone call unwatch inside the
+ // watchHandler.
+ var watchersCache = watchers.concat();
+ for (var i = 0, l = watchersCache.length; i < l; ++i) {
+ var watcher = watchersCache[i];
+ if (!watcher) {
+ window.console.log('wrapper\'s watch for ' + propertyName +
+ ' is unavailable!');
+ continue; // should never happend
+ }
+ originalValue = userValue;
+ try {
+ userValue = watcher(oldValue, userValue, 'set');
+ } catch (e) {
+ userValue = originalValue;
+ }
+ }
+ }
+ internalObj[propertyName] = userValue;
+ });
+ };
+
+ this.cleanUp_ = function() {
+ This.window_.removeEventListener('unload', This.cleanUp_, false);
+
+ // Delete all properties
+ for (var i = 0, l = This.properties_.length; i < l; ++i) {
+ delete This.wrapper_[This.properties_[i]];
+ }
+ This.window_ = null;
+ This.wrapper_ = null;
+ This.properties_ = null;
+ This.watcherTable_ = null;
+ This.propertyNameFilter_ = null;
+ This = null;
+ }
+
+ // We only bridge the non-function properties.
+ for (var prop in originalObject) {
+ if (this.propertyNameFilter_ && this.propertyNameFilter_.test(prop)) {
+ this.propertyNameFilter_.test('');
+ continue;
+ }
+ if (typeof originalObject[prop] != 'function') {
+ this.properties_.push(prop);
+ setGetterAndSetter(this.wrapper_, prop);
+ }
+ }
+
+ // Listen the unload event.
+ this.window_.addEventListener('unload', this.cleanUp_, false);
+};
+
+__screenCapturePageContext__.ObjectWrapDelegate.prototype.getWrapper =
+ function() {
+ return this.wrapper_;
+}
+
+// Check whether a property is in the wrapper or not. If yes, return true.
+// Otherwise return false.
+__screenCapturePageContext__.ObjectWrapDelegate.prototype.hasProperty =
+ function(propertyName) {
+ for (var i = 0, l = this.properties_.length; i < l; ++i) {
+ if (propertyName == this.properties_[i])
+ return true;
+ }
+ return false;
+}
+
+// Watches for a property to be accessed or be assigned a value and runs a
+// function when that occurs.
+// Watches for accessing a property or assignment to a property named prop in
+// this object, calling handler(oldval, newval, reason) whenever prop is
+// get/set and storing the return value in that property.
+// A watchpoint can filter (or nullify) the value assignment, by returning a
+// modified newval (or by returning oldval).
+// When watchpoint is trigering by get opeartor, the oldval is equal with
+// newval. The reason will be 'get'.
+// When watchpoint is trigering by set opeartor, The reason will be 'set'.
+// If you delete a property for which a watchpoint has been set,
+// that watchpoint does not disappear. If you later recreate the property,
+// the watchpoint is still in effect.
+// To remove a watchpoint, use the unwatch method.
+// If register the watchpoint successfully, return true. Otherwise return false.
+__screenCapturePageContext__.ObjectWrapDelegate.prototype.watch = function(
+ propertyName, watchHandler) {
+ if (!this.hasProperty(propertyName))
+ return false;
+ var watchers = this.watcherTable_[propertyName];
+ if (watchers) {
+ for (var i = 0, l = watchers.length; i < l; ++i) {
+ if (watchHandler == watchers[i])
+ return true;
+ }
+ } else {
+ watchers = new Array();
+ this.watcherTable_[propertyName] = watchers;
+ }
+ watchers.push(watchHandler);
+ return true;
+}
+
+// Removes a watchpoint set with the watch method.
+__screenCapturePageContext__.ObjectWrapDelegate.prototype.unwatch = function(
+ propertyName, watchHandler) {
+ if (!this.hasProperty(propertyName))
+ return false;
+ var watchers = this.watcherTable_[propertyName];
+ if (watchers) {
+ for (var i = 0, l = watchers.length; i < l; ++i) {
+ if (watchHandler == watchers[i]) {
+ watchers.splice(i, 1);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+__screenCapturePageContext__.init();