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();