Projects‎ > ‎

PA 8: Scripting in a Web Browser


Due Date

Monday, April 15, 2013, 11:59 PM

Team

You will work in the same teams as in last PA. Furthermore, you will reuse the same repository you used for all the PAs so far. See the Starter-kit section.

If you have changed partners, please email cs164@imail.eecs.berkeley.edu with the following information:

  • You and your partners' names and instructional accounts.
  • The URL of the bitbucket repository that you will use. You may create a new one by following the same steps as in PA2

Suggested Steps

  1. Add scripting support for your browser. Extend the DOM nodes with a Script tag <Script ref="myscript.164"></Script>. This DOM node has no children. The tag executes the code in myscript.164 in pass 5 of the layout engine. You do not need to support in-line scripts in between the start and end tags.
  2. Add BQuery support, first without the end() primitive.
  3. Add end() to your BQuery engine.

Notes:

  • The files ./pa8/library.164 and ./pa8/browser.py have been updated and new versions are included in the starter kit.
  • Please ignore the comment on pbar and ibox dependencies in ./pa7/runtime.py

Scripting

In this part you will make it possible for page authors to attach scripts to browser events.  We ask you to support only one event (onclick) but feel free to add more. We have already implemented the code that will invoke the script attached to the onclick event, but you are free to add more (see Node.pass5_BindEventListeners in node.164). (edited on 4/12)

Example: You need to be able to handle a web page like this.  Note the 164 scripts attached to the DOM using the onclick attribute.  These scripts are invoked when the DOM element or its child is clicked.  These scripts modify the DOM.  After any such script (aka handler or callback) is invoked, the DOM is relaid out and redrawn.

<HBox fill="orange">
  <HBox fract="45" onclick="self.fract=75" fill="lightblue" klass="myclass">
    Click here to grow
  </HBox>
  <HBox fract="55" onclick="bq('myclass'):fill('green')" fill="yellow" klass="fontclass">
    Click here to change color of other nodes
  </HBox>
  <HBox fract="25" onclick="bq('fontclass'):font('8'):fill('ivory2')" fill="white" klass="myclass">
    Click to shrink font
  </HBox>
  <Link ref="tests/bah/test5.bah" klass="myclass">
    Click here to reload this page.
  </Link>
</HBox>

Note on the Script tag: Execute the program linked from the Script tag during pass 5 of the layout.  The Script tags should be execute only once.

What you need to do in this part is to handle executing the program linked from "ref" attribute in the Script tag during pass 5 of the layout, and the script tags should be executed only once. We will need to create Script prototype object and its necessary methods in browser/script.164. Hint: evaluateScript function in runtime.py might become handy in your implementation. (edited on 4/12)

BQuery

The second part of this project is to add a JQuery-like DSL to your browser. The onclick handlers in this example above invoke BQuery, whose entire implementation yours to write.  Note that we extended the Object class with the self-explanatory method class().  We leave its implementation to you.  Also note that the CSS notion of "class" is called klass in our implementation.  The rationale is to avoid conflict with the method class in the class Object. Have a look at the lecture slides to refresh your memory.

The second part of this project is to add a JQuery-like DSL to your browser. The onclick handlers in this example above invoke BQuery. You do not have to worry about how BQuery will be invoked. That part of the implementation is already given in the starter kit. (edited on 4/12)

Note that the CSS notion of "class" is called klass in our implementation.  The rationale is to avoid conflict with the method class in the class Object if you decide that we need that method.

Your BQuery should work very much like the official JQuery. You are strongly suggested to read the JQuery documentation at http://docs.jquery.com/Main_Page

Your BQuery implementation must support (edited on 4/12):

bquery(klass) or bq(klass)
Return a selection which is a BQuery object containing all the elements of the specified class. klass is a string.

Your BQuery object must support the following methods (edited on 4/12):
  1. filter(fun)
    Throw away all elements such that fun(element) is not True. fun is a function.
  2. map(fun)
    Produce a new selection by applying fun on each elements of the current selection. fun is a function.
  3. each(fun)
    Apply fun on all elements currently selected. fun is a function. 
  4. children()
    Return a new selection consisting of the immediate children of the currently selected elements.
  5. parent()
    Return a new selection consisting of the immediate parents of the currently selected elements.
  6. parents()
    Return a new selection consisting of all the ancestors of the currently selected elements. This is the reflexive-transitive closure of the parent relationship. (edited on 4/14)
  7. end()
    Remove the most recent effect of filtering operation in the current chain and return the set of matched elements to its previous state. Only filter() is affected by end().  In other words, only filter pushes a new list of elements onto the stack.
  8. clone() 
    Deep-copy every selected element.
  9. append(content)
    Insert content to the end of each element in the set of matched elements. content is a TML program encoded as a string.
  10. appendTo(klass) 
    Insert every element currently selected to the end of the each element of class klass. klass is a string.
  11. click(code)
    Replace the onclick handler of the node currently selected by code. Code is a 164 program encoded as a string.
  12. first()
    Return a new selection consisting of the first element in the current selection.
  13. background(color)
    Change the background of the elements currently selected. color is a string.
  14. font(size)
    Change the font size of the elements currently selected. size is a number encoded as a string. 

To keep things simple, parent() and parents() do not need to filter out duplicate nodes from the list that they return.

Example

The following document contains one box whose text shrinks when clicked

<HBox fract="25" onclick="bq('fontclass'):font('10'):background('moccasin')" background="white"   klass="myclass">      Click to shrink font </HBox>

Implementation

Your are free the design your code the way you want. We do not give you any guidelines, so take some time before starting coding to think about design and the issues you might encounter.

You can see below the entire implementation of the JQuery engine described in the slides. This one is very crude/limited, yours will probably use a very different architecture. This is not an example of how to implement BQuery.

# bQuery: a crude little jQuery-like language
#
# usage: bq('myklass'):fill('ivory2'):font('Helvetica 8')
#
def Q = Object:new({})
def bq(klass) { Q:new({klass=klass}) }
Q.each = lambda(self,f) {
    for n in preorder(window) {
        if ("klass" in n) {
            if (n.klass == self.klass) { f(n) }
        }
    }
    # return the query object to allow call chaining
    self
}

Q.fill = lambda(self,color) { self:each(lambda(node) { node.fill = color }) }
Q.font = lambda(self,font) { self:each(
    lambda(node) {
        for n in preorder(node) {
            if (n:class() == Word) { n.font = font }
        }
    })
}

Clone

To implement the clone method, you will need to perform deep copy of nodes. Here an incomplete skeleton to save you busy work.

## Deep copy, used in bQuery:clone
#

Node.clone = lambda(self) {
  # all attributesare copied to the new object
  def clone = {}

  #TODO Fix prototype links!
 
  clone.name   = self.name
  clone.x      = self.x

  clone.y      = self.y
  clone.width  = self.width
  clone.height = self.height
  clone.color  = self.color
  clone.opacity = self.opacity
 
  clone.topMargin     = self.topMargin    
  clone.rightMargin   = self.rightMargin 
  clone.bottomMargin  = self.bottomMargin 
  clone.leftMargin    = self.leftMargin   
  clone.topPadding    = self.topPadding   
  clone.rightPadding  = self.rightPadding 
  clone.bottomPadding = self.bottomPadding
  clone.leftPadding   = self.leftPadding  
 
  if ("klass" in self) { clone.klass = self.klass }
 
  clone.__eventListenersBound=0 # We do not rebind event listener
  clone.___qt = null # the clone will receive its qtwidget during rendering
  clone
}

Performance

There are two ways to implement call chaining in JQuery.

  • Pass a list of selected nodes along the call chain.
  • Pass an iterator factory (which produces iterators over selected nodes) along the call chain.

For performance reason, the second option is the better one. You are obliged to use it! and will be dock down if you pass lists of nodes around.

Demo

You can see in this video how ./pa8/tests/bquery/demo.tml reacts to mouse clicks.

Deliverables

  1. Scripting support for your browser.
  2. The code for the new Script node, stored in ./pa8/browser/script.164
  3. Your BQuery engine, stored in ./pa8/browser/bquery.164  (Yes, case matters!)
  4. A fancy document exploiting scripting and Jquery. Be creative! This should be stored in ./pa8/demo.tml


Logistics

Starter-kit

You can fetch the starter-kit by running the following Mercurial commands:

  1. hg pull https://<your username>@bitbucket.org/cs164_overlord/cs164sp13
  2. hg merge or hg update, whichever Mercurial tells you to run.

There is no starter-kit for this PA, except for corrections, a grader, and a few testcases you must pass. Copy you web-browser from PA7 into the new pa8 directory, as described in the README file.

Errata and Bug fixes

Once a bug is discovered in the starter-kit, we will publish a corrected version on Bitbucket. Then, you can update your starter-kit (and get the bugfix) with following commands:

  1. hg pull https://<your username>@bitbucket.org/cs164_overlord/cs164sp13
  2. hg merge or hg update, whichever Mercurial tells you to run.

Grading

We will use an autograder to test your BQuery implementation. Make sure you pass all tests in the starter kit:

  • The input/output pairs in the folder ./pa8/tests/bquery-grader test the behavior of your BQuery implementation on page.tml in the same folder. You may add your own tests to use with the autograder by following the same convention.
  • You should additionally ensure that your implementation works with the examples in ./pa8/tests/bquery.

Furthermore, you should not rely on our infrastructure any more. Make sure you use your own parser/interpreter/browser. Your submission must include your parser/interpreter/browser. Use hg add when needed.

Submission

The Submission Itself

You will submit by tagging the revision you want us to grade with the following tag: "PA8_SUBMIT". Case matters. To do so, please follow the instructions below:

  1. Make sure you pass all the tests included with the starter kit. To do so, all debugging print statements must be removed.
  2. Please don't forget to add all your files to the repository (use hg add). This includes the parser code, the grammar, the interpreter, the browser and the test files. Do NOT add any *.pyc files.
  3. Commit any outstanding modifications with "hg commit -m message_here"
  4. Commit with  "hg commit -m message_here"
  5. Add the tag with the command "hg tag PA8_SUBMIT"
  6. Publish the project on the server with  "hg push"
  7. Go to  https://bitbucket.org/<username>/cs164sp13/changesets
    You should see the tag in the timeline. If you do, you are all set.

Do not forget steps 4 & 5, otherwise your tag will not be visible to our autograder.

If you have discovered a last minute bug, you can remove the tag as follows:

  1. Run "hg tag --remove PA8_SUBMIT"
  2. Commit with  "hg commit -m message_here"
  3. Publish with  "hg push"
Then you can keep working on the project and add the tag again when you are ready to submit.
Comments